@zereight/mcp-gitlab 1.0.77 → 2.0.2
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/index.js +214 -4082
- package/build/src/argon2wrapper.js +68 -0
- package/build/src/authentication.js +78 -0
- package/build/src/authhelpers.js +44 -0
- package/build/src/config.js +99 -0
- package/build/{customSchemas.js → src/customSchemas.js} +0 -12
- package/build/src/gitlabhandler.js +1666 -0
- package/build/src/gitlabsession.js +103 -0
- package/build/src/logger.js +11 -0
- package/build/src/mcpserver.js +1344 -0
- package/build/src/oauth.js +389 -0
- package/build/{schemas.js → src/schemas.js} +1 -0
- package/package.json +8 -3
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
|
|
2
|
+
import { config } from './config.js';
|
|
3
|
+
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
import argon2 from './argon2wrapper.js';
|
|
6
|
+
import { randomBytes } from 'crypto';
|
|
7
|
+
// Custom provider that handles dynamic registration and maps to GitLab OAuth
|
|
8
|
+
class GitLabProxyProvider extends ProxyOAuthServerProvider {
|
|
9
|
+
// Static async factory method
|
|
10
|
+
static async New(options) {
|
|
11
|
+
// we put this here so we dont initialize this unless we are using the oauth provider
|
|
12
|
+
const Database = (await import('better-sqlite3')).default;
|
|
13
|
+
const db = new Database(config.GITLAB_OAUTH2_DB_PATH);
|
|
14
|
+
// Create tables if they don't exist
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
17
|
+
client_id TEXT PRIMARY KEY,
|
|
18
|
+
client_secret TEXT NOT NULL,
|
|
19
|
+
redirect_uris TEXT NOT NULL,
|
|
20
|
+
grant_types TEXT NOT NULL,
|
|
21
|
+
response_types TEXT NOT NULL,
|
|
22
|
+
token_endpoint_auth_method TEXT NOT NULL,
|
|
23
|
+
client_id_issued_at INTEGER NOT NULL,
|
|
24
|
+
metadata TEXT NOT NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS client_redirect_uris (
|
|
28
|
+
client_id TEXT PRIMARY KEY,
|
|
29
|
+
redirect_uris TEXT NOT NULL
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS state_mappings (
|
|
33
|
+
state TEXT PRIMARY KEY,
|
|
34
|
+
redirect_uri TEXT NOT NULL,
|
|
35
|
+
timestamp INTEGER NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS access_tokens (
|
|
39
|
+
token_hash TEXT PRIMARY KEY,
|
|
40
|
+
client_id TEXT NOT NULL,
|
|
41
|
+
scopes TEXT NOT NULL,
|
|
42
|
+
expires_at INTEGER,
|
|
43
|
+
timestamp INTEGER NOT NULL
|
|
44
|
+
);
|
|
45
|
+
`);
|
|
46
|
+
const provider = new GitLabProxyProvider(options, db);
|
|
47
|
+
return provider;
|
|
48
|
+
}
|
|
49
|
+
// State expiry time in milliseconds (15 minutes)
|
|
50
|
+
STATE_EXPIRY_MS = 15 * 60 * 1000;
|
|
51
|
+
// Token expiry time in milliseconds (7 days)
|
|
52
|
+
TOKEN_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
53
|
+
// Cleanup interval
|
|
54
|
+
cleanupInterval;
|
|
55
|
+
db;
|
|
56
|
+
constructor(options, db) {
|
|
57
|
+
super(options);
|
|
58
|
+
this.db = db;
|
|
59
|
+
// Start cleanup interval
|
|
60
|
+
this.cleanupInterval = setInterval(() => {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
try {
|
|
63
|
+
// Clean up expired state mappings
|
|
64
|
+
db.prepare('DELETE FROM state_mappings WHERE timestamp < ?').run(now - this.STATE_EXPIRY_MS);
|
|
65
|
+
// Clean up expired tokens
|
|
66
|
+
db.prepare('DELETE FROM access_tokens WHERE timestamp < ? OR (expires_at IS NOT NULL AND expires_at < ?)')
|
|
67
|
+
.run(now - this.TOKEN_EXPIRY_MS, Math.floor(now / 1000));
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
logger.error('Error during cleanup:', err);
|
|
71
|
+
}
|
|
72
|
+
}, 5 * 60 * 1000);
|
|
73
|
+
}
|
|
74
|
+
get clientsStore() {
|
|
75
|
+
return {
|
|
76
|
+
getClient: async (clientId) => {
|
|
77
|
+
// Check if this is the actual GitLab client
|
|
78
|
+
if (clientId === config.GITLAB_OAUTH2_CLIENT_ID) {
|
|
79
|
+
return {
|
|
80
|
+
client_id: config.GITLAB_OAUTH2_CLIENT_ID,
|
|
81
|
+
client_secret: config.GITLAB_OAUTH2_CLIENT_SECRET,
|
|
82
|
+
redirect_uris: [config.GITLAB_OAUTH2_REDIRECT_URL],
|
|
83
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
84
|
+
response_types: ['code'],
|
|
85
|
+
token_endpoint_auth_method: 'client_secret_post'
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Check if this is a registered dynamic client
|
|
89
|
+
const row = this.db.prepare('SELECT * FROM oauth_clients WHERE client_id = ?').get(clientId);
|
|
90
|
+
if (!row) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const client = {
|
|
94
|
+
...JSON.parse(row.metadata),
|
|
95
|
+
client_id: row.client_id,
|
|
96
|
+
client_secret: row.client_secret,
|
|
97
|
+
redirect_uris: JSON.parse(row.redirect_uris),
|
|
98
|
+
grant_types: JSON.parse(row.grant_types),
|
|
99
|
+
response_types: JSON.parse(row.response_types),
|
|
100
|
+
token_endpoint_auth_method: row.token_endpoint_auth_method,
|
|
101
|
+
client_id_issued_at: row.client_id_issued_at
|
|
102
|
+
};
|
|
103
|
+
return client;
|
|
104
|
+
},
|
|
105
|
+
registerClient: async (clientMetadata) => {
|
|
106
|
+
// Generate a unique client ID for this MCP client using crypto-safe random
|
|
107
|
+
const randomId = randomBytes(16).toString('hex');
|
|
108
|
+
const clientId = `mcp_${Date.now()}_${randomId}`;
|
|
109
|
+
// Generate a secure client secret
|
|
110
|
+
const randomSecret = randomBytes(32).toString('hex');
|
|
111
|
+
// Create the client registration
|
|
112
|
+
const client = {
|
|
113
|
+
...clientMetadata,
|
|
114
|
+
client_id: clientId,
|
|
115
|
+
client_secret: `secret_${randomSecret}`,
|
|
116
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
117
|
+
grant_types: clientMetadata.grant_types || ['authorization_code', 'refresh_token'],
|
|
118
|
+
response_types: clientMetadata.response_types || ['code'],
|
|
119
|
+
token_endpoint_auth_method: clientMetadata.token_endpoint_auth_method || 'client_secret_post'
|
|
120
|
+
};
|
|
121
|
+
// Store the client in database
|
|
122
|
+
try {
|
|
123
|
+
// Store client
|
|
124
|
+
this.db.prepare(`
|
|
125
|
+
INSERT INTO oauth_clients
|
|
126
|
+
(client_id, client_secret, redirect_uris, grant_types, response_types,
|
|
127
|
+
token_endpoint_auth_method, client_id_issued_at, metadata)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
129
|
+
`).run(client.client_id, client.client_secret, JSON.stringify(client.redirect_uris), JSON.stringify(client.grant_types), JSON.stringify(client.response_types), client.token_endpoint_auth_method, client.client_id_issued_at, JSON.stringify(clientMetadata));
|
|
130
|
+
// Store redirect URIs
|
|
131
|
+
this.db.prepare('INSERT INTO client_redirect_uris (client_id, redirect_uris) VALUES (?, ?)')
|
|
132
|
+
.run(clientId, JSON.stringify(clientMetadata.redirect_uris || []));
|
|
133
|
+
return client;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Override authorization to use GitLab OAuth credentials
|
|
142
|
+
async authorize(client, params, res) {
|
|
143
|
+
// Store the mapping between state and client's actual redirect URI with timestamp
|
|
144
|
+
if (params.state && params.redirectUri) {
|
|
145
|
+
try {
|
|
146
|
+
this.db.prepare('INSERT OR REPLACE INTO state_mappings (state, redirect_uri, timestamp) VALUES (?, ?, ?)')
|
|
147
|
+
.run(params.state, params.redirectUri, Date.now());
|
|
148
|
+
logger.debug(`Stored state mapping: ${params.state} -> ${params.redirectUri} at ${new Date().toISOString()}`);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
logger.error('Error storing state mapping:', err);
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Construct the authorization URL directly to ensure proper formatting
|
|
156
|
+
const authUrl = new URL(this._endpoints.authorizationUrl);
|
|
157
|
+
// Add required OAuth parameters
|
|
158
|
+
authUrl.searchParams.set('client_id', config.GITLAB_OAUTH2_CLIENT_ID);
|
|
159
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
160
|
+
authUrl.searchParams.set('redirect_uri', config.GITLAB_OAUTH2_REDIRECT_URL.trim());
|
|
161
|
+
authUrl.searchParams.set('code_challenge', params.codeChallenge);
|
|
162
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
163
|
+
// Add optional parameters
|
|
164
|
+
if (params.state) {
|
|
165
|
+
authUrl.searchParams.set('state', params.state);
|
|
166
|
+
}
|
|
167
|
+
const gitlabScopes = ['api', 'openid', 'profile', 'email'];
|
|
168
|
+
authUrl.searchParams.set('scope', gitlabScopes.join(' '));
|
|
169
|
+
// GitLab doesn't support the 'resource' parameter, so we skip it
|
|
170
|
+
logger.debug({
|
|
171
|
+
url: authUrl.toString(),
|
|
172
|
+
scopes: gitlabScopes,
|
|
173
|
+
requested_scopes: params.scopes
|
|
174
|
+
}, `Redirecting to GitLab OAuth`);
|
|
175
|
+
// Redirect to GitLab
|
|
176
|
+
res.redirect(authUrl.toString());
|
|
177
|
+
}
|
|
178
|
+
// Method to get redirect URI from state
|
|
179
|
+
async getRedirectUriFromState(state) {
|
|
180
|
+
const row = this.db.prepare('SELECT redirect_uri, timestamp FROM state_mappings WHERE state = ?').get(state);
|
|
181
|
+
if (!row) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
redirectUri: row.redirect_uri,
|
|
186
|
+
timestamp: row.timestamp
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Method to delete state mapping
|
|
190
|
+
async deleteStateMapping(state) {
|
|
191
|
+
this.db.prepare('DELETE FROM state_mappings WHERE state = ?').run(state);
|
|
192
|
+
}
|
|
193
|
+
// Method to verify token
|
|
194
|
+
async verifyToken(token) {
|
|
195
|
+
// Get all token hashes to check against
|
|
196
|
+
const rows = this.db.prepare('SELECT * FROM access_tokens').all();
|
|
197
|
+
// Find the matching token by verifying against each hash
|
|
198
|
+
let matchingRow = null;
|
|
199
|
+
for (const row of rows) {
|
|
200
|
+
try {
|
|
201
|
+
if (await argon2.verify(row.token_hash, token)) {
|
|
202
|
+
matchingRow = row;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
// Skip invalid hashes
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!matchingRow) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
// Check if token has expired by timestamp
|
|
216
|
+
if (now - matchingRow.timestamp > this.TOKEN_EXPIRY_MS) {
|
|
217
|
+
await this.deleteAccessTokenByHash(matchingRow.token_hash);
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
// Check if token has an explicit expiry time
|
|
221
|
+
if (matchingRow.expires_at && matchingRow.expires_at < Math.floor(now / 1000)) {
|
|
222
|
+
await this.deleteAccessTokenByHash(matchingRow.token_hash);
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
const authInfo = {
|
|
226
|
+
token: token, // Return the original token
|
|
227
|
+
clientId: matchingRow.client_id,
|
|
228
|
+
scopes: JSON.parse(matchingRow.scopes),
|
|
229
|
+
expiresAt: matchingRow.expires_at ?? undefined
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
authInfo,
|
|
233
|
+
gitlabToken: token, // Return the original token since we don't store gitlab_token anymore
|
|
234
|
+
timestamp: matchingRow.timestamp
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Helper method to delete access token by hash
|
|
238
|
+
async deleteAccessTokenByHash(tokenHash) {
|
|
239
|
+
this.db.prepare('DELETE FROM access_tokens WHERE token_hash = ?').run(tokenHash);
|
|
240
|
+
}
|
|
241
|
+
// Override token exchange to use GitLab OAuth credentials
|
|
242
|
+
async exchangeAuthorizationCode(client, authorizationCode, codeVerifier, redirectUri, resource) {
|
|
243
|
+
// Use GitLab OAuth credentials for token exchange
|
|
244
|
+
const gitlabClient = {
|
|
245
|
+
...client,
|
|
246
|
+
client_id: config.GITLAB_OAUTH2_CLIENT_ID,
|
|
247
|
+
client_secret: config.GITLAB_OAUTH2_CLIENT_SECRET,
|
|
248
|
+
redirect_uris: [config.GITLAB_OAUTH2_REDIRECT_URL]
|
|
249
|
+
};
|
|
250
|
+
// Use GitLab's redirect URI for the token exchange
|
|
251
|
+
const tokens = await super.exchangeAuthorizationCode(gitlabClient, authorizationCode, codeVerifier, config.GITLAB_OAUTH2_REDIRECT_URL, resource);
|
|
252
|
+
// Store the token mapping for our own verification
|
|
253
|
+
if (tokens.access_token) {
|
|
254
|
+
const expiresAt = tokens.expires_in ? Math.floor(Date.now() / 1000) + tokens.expires_in : null;
|
|
255
|
+
const scopes = tokens.scope ? tokens.scope.split(' ') : [];
|
|
256
|
+
try {
|
|
257
|
+
// Hash the token before storing
|
|
258
|
+
const tokenHash = await argon2.hash(tokens.access_token);
|
|
259
|
+
this.db.prepare(`
|
|
260
|
+
INSERT OR REPLACE INTO access_tokens
|
|
261
|
+
(token_hash, client_id, scopes, expires_at, timestamp)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?)
|
|
263
|
+
`).run(tokenHash, client.client_id, JSON.stringify(scopes), expiresAt, Date.now());
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
logger.error('Error storing access token:', err);
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return tokens;
|
|
271
|
+
}
|
|
272
|
+
// Override verifyAccessToken to use our internal token store
|
|
273
|
+
async verifyAccessToken(token) {
|
|
274
|
+
const tokenInfo = await this.verifyToken(token);
|
|
275
|
+
if (!tokenInfo) {
|
|
276
|
+
throw new Error('Invalid or expired token');
|
|
277
|
+
}
|
|
278
|
+
return tokenInfo.authInfo;
|
|
279
|
+
}
|
|
280
|
+
// Handle OAuth callback and redirect to client's actual callback URL
|
|
281
|
+
handleOAuthCallback = async (req, res) => {
|
|
282
|
+
const { code, state, error, error_description } = req.query;
|
|
283
|
+
logger.debug({ code: !!code, state, error }, 'OAuth callback received');
|
|
284
|
+
if (!state) {
|
|
285
|
+
res.status(400).send('Missing state parameter');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
// Get the client's actual redirect URI with timestamp
|
|
290
|
+
const stateMapping = await this.getRedirectUriFromState(state);
|
|
291
|
+
if (!stateMapping) {
|
|
292
|
+
logger.error(`No redirect URI found for state: ${state}`);
|
|
293
|
+
res.status(400).send('Invalid state parameter');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Check if the state mapping has expired
|
|
297
|
+
if (Date.now() - stateMapping.timestamp > this.STATE_EXPIRY_MS) {
|
|
298
|
+
logger.error(`State mapping expired for state: ${state}`);
|
|
299
|
+
await this.deleteStateMapping(state);
|
|
300
|
+
res.status(400).send('State parameter expired');
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const clientRedirectUri = stateMapping.redirectUri;
|
|
304
|
+
// Clean up the state mapping
|
|
305
|
+
await this.deleteStateMapping(state);
|
|
306
|
+
// Build the redirect URL with all parameters
|
|
307
|
+
const redirectUrl = new URL(clientRedirectUri);
|
|
308
|
+
// Pass through all query parameters
|
|
309
|
+
if (code)
|
|
310
|
+
redirectUrl.searchParams.set('code', code);
|
|
311
|
+
if (state)
|
|
312
|
+
redirectUrl.searchParams.set('state', state);
|
|
313
|
+
if (error)
|
|
314
|
+
redirectUrl.searchParams.set('error', error);
|
|
315
|
+
if (error_description)
|
|
316
|
+
redirectUrl.searchParams.set('error_description', error_description);
|
|
317
|
+
if (error) {
|
|
318
|
+
logger.debug({ error }, "oauth callback error");
|
|
319
|
+
}
|
|
320
|
+
logger.debug(`sending redirecting to client callback ${state}`);
|
|
321
|
+
// Redirect to the client's actual callback URL
|
|
322
|
+
res.redirect(redirectUrl.toString());
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
logger.error('Error handling OAuth callback:', err);
|
|
326
|
+
res.status(500).send('Internal server error');
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
// Create OAuth2 router
|
|
330
|
+
createOAuth2Router() {
|
|
331
|
+
if (!config.GITLAB_OAUTH2_BASE_URL) {
|
|
332
|
+
throw new Error("GITLAB_OAUTH2_BASE_URL is not set");
|
|
333
|
+
}
|
|
334
|
+
return mcpAuthRouter({
|
|
335
|
+
issuerUrl: new URL(config.GITLAB_OAUTH2_BASE_URL),
|
|
336
|
+
baseUrl: new URL(config.GITLAB_OAUTH2_BASE_URL),
|
|
337
|
+
authorizationOptions: {},
|
|
338
|
+
provider: this,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// Create token verifier
|
|
342
|
+
createTokenVerifier() {
|
|
343
|
+
const tokenVerifier = {
|
|
344
|
+
verifyAccessToken: async (token) => {
|
|
345
|
+
return this.verifyAccessToken(token);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
return tokenVerifier;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// Export the provider class
|
|
352
|
+
export { GitLabProxyProvider };
|
|
353
|
+
// Create the GitLab OAuth provider
|
|
354
|
+
export const createGitLabOAuthProvider = async () => {
|
|
355
|
+
if (!config.GITLAB_OAUTH2_AUTHORIZATION_URL) {
|
|
356
|
+
throw new Error("GITLAB_OAUTH2_AUTHORIZATION_URL is not set");
|
|
357
|
+
}
|
|
358
|
+
if (!config.GITLAB_OAUTH2_CLIENT_ID) {
|
|
359
|
+
throw new Error("GITLAB_OAUTH2_CLIENT_ID is not set");
|
|
360
|
+
}
|
|
361
|
+
if (!config.GITLAB_OAUTH2_REDIRECT_URL) {
|
|
362
|
+
throw new Error("GITLAB_OAUTH2_REDIRECT_URIS is not set");
|
|
363
|
+
}
|
|
364
|
+
if (!config.GITLAB_OAUTH2_TOKEN_URL) {
|
|
365
|
+
throw new Error("GITLAB_OAUTH2_TOKEN_URL is not set");
|
|
366
|
+
}
|
|
367
|
+
if (!config.GITLAB_OAUTH2_ISSUER_URL) {
|
|
368
|
+
throw new Error("GITLAB_OAUTH2_ISSUER_URL is not set");
|
|
369
|
+
}
|
|
370
|
+
if (!config.GITLAB_OAUTH2_BASE_URL) {
|
|
371
|
+
throw new Error("GITLAB_OAUTH2_BASE_URL is not set");
|
|
372
|
+
}
|
|
373
|
+
const provider = await GitLabProxyProvider.New({
|
|
374
|
+
endpoints: {
|
|
375
|
+
authorizationUrl: config.GITLAB_OAUTH2_AUTHORIZATION_URL,
|
|
376
|
+
tokenUrl: config.GITLAB_OAUTH2_TOKEN_URL,
|
|
377
|
+
revocationUrl: config.GITLAB_OAUTH2_REVOCATION_URL,
|
|
378
|
+
},
|
|
379
|
+
verifyAccessToken: async () => {
|
|
380
|
+
// This will be overridden by the class method
|
|
381
|
+
throw new Error('Should not be called');
|
|
382
|
+
},
|
|
383
|
+
getClient: async (client_id) => {
|
|
384
|
+
// This is handled by our custom provider's clientsStore
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return provider;
|
|
389
|
+
};
|
|
@@ -1578,6 +1578,7 @@ export const GetCommitDiffSchema = z.object({
|
|
|
1578
1578
|
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
1579
1579
|
sha: z.string().describe("The commit hash or name of a repository branch or tag"),
|
|
1580
1580
|
});
|
|
1581
|
+
export const GetCurrentUserSchema = z.object({});
|
|
1581
1582
|
// Schema for listing issues assigned to the current user
|
|
1582
1583
|
export const MyIssuesSchema = z.object({
|
|
1583
1584
|
project_id: z.string().optional().describe("Project ID or URL-encoded path (optional when GITLAB_PROJECT_ID is set)"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "MCP server for using the GitLab API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "zereight",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"prepare": "npm run build",
|
|
21
21
|
"dev": "npm run build && node build/index.js",
|
|
22
22
|
"watch": "tsc --watch",
|
|
23
|
-
"deploy": "npm publish --access public",
|
|
23
|
+
"npm:deploy": "npm publish --access public",
|
|
24
|
+
"npm:deploy:beta": "npm publish --access public --tag beta",
|
|
24
25
|
"generate-tools": "npx ts-node scripts/generate-tools-readme.ts",
|
|
25
26
|
"changelog": "auto-changelog -p",
|
|
26
27
|
"test": "node test/validate-api.js",
|
|
@@ -32,8 +33,11 @@
|
|
|
32
33
|
"format:check": "prettier --check \"**/*.{js,ts,json,md}\""
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@modelcontextprotocol/sdk": "
|
|
36
|
+
"@modelcontextprotocol/sdk": "1.13.3",
|
|
37
|
+
"@noble/hashes": "^1.8.0",
|
|
38
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
36
39
|
"@types/node-fetch": "^2.6.12",
|
|
40
|
+
"better-sqlite3": "^12.2.0",
|
|
37
41
|
"express": "^5.1.0",
|
|
38
42
|
"fetch-cookie": "^3.1.0",
|
|
39
43
|
"form-data": "^4.0.0",
|
|
@@ -43,6 +47,7 @@
|
|
|
43
47
|
"pino": "^9.7.0",
|
|
44
48
|
"pino-pretty": "^13.0.0",
|
|
45
49
|
"socks-proxy-agent": "^8.0.5",
|
|
50
|
+
"sqlite3": "^5.1.7",
|
|
46
51
|
"tough-cookie": "^5.1.2",
|
|
47
52
|
"zod-to-json-schema": "^3.23.5"
|
|
48
53
|
},
|