Merge pull request #10 from chad-loder/url-normalization-fix
Fix URL construction with smart API URL normalization
This commit is contained in:
@ -8,7 +8,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|||||||
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, CreateNoteSchema, } from "./schemas.js";
|
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, CreateNoteSchema, } from "./schemas.js";
|
||||||
const server = new Server({
|
const server = new Server({
|
||||||
name: "better-gitlab-mcp-server",
|
name: "better-gitlab-mcp-server",
|
||||||
version: "0.0.1",
|
version: "1.0.7-fix",
|
||||||
}, {
|
}, {
|
||||||
capabilities: {
|
capabilities: {
|
||||||
tools: {},
|
tools: {},
|
||||||
@ -345,14 +345,33 @@ async function updateMergeRequest(projectId, mergeRequestIid, options) {
|
|||||||
async function createNote(projectId, noteableType, // 'issue' 또는 'merge_request' 타입 명시
|
async function createNote(projectId, noteableType, // 'issue' 또는 'merge_request' 타입 명시
|
||||||
noteableIid, body) {
|
noteableIid, body) {
|
||||||
// ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능
|
// ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능
|
||||||
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/${noteableType}/${noteableIid}/notes`);
|
// Don't add /api/v4 again since it's already included in GITLAB_API_URL
|
||||||
const response = await fetch(url.toString(), {
|
console.error("DEBUG - createNote - GITLAB_API_URL from env: " + process.env.GITLAB_API_URL);
|
||||||
method: "POST",
|
console.error("DEBUG - createNote - GITLAB_API_URL in code: " + GITLAB_API_URL);
|
||||||
headers: DEFAULT_HEADERS,
|
const url = new URL(`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/${noteableType}s/${noteableIid}/notes` // Using plural form (issues/merge_requests) as per GitLab API documentation
|
||||||
body: JSON.stringify({ body }),
|
);
|
||||||
});
|
// Add some debug logging
|
||||||
await handleGitLabError(response);
|
console.error("DEBUG - createNote function called");
|
||||||
return await response.json(); // ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능, 필요하면 스키마 정의
|
console.error(`DEBUG - createNote - URL: ${url.toString()}`);
|
||||||
|
console.error(`DEBUG - createNote - projectId: ${projectId}, noteableType: ${noteableType}, noteableIid: ${noteableIid}`);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
headers: DEFAULT_HEADERS,
|
||||||
|
body: JSON.stringify({ body }),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error(`DEBUG - createNote - Error response: ${response.status} ${response.statusText}`);
|
||||||
|
console.error(`DEBUG - createNote - Error body: ${errorText}`);
|
||||||
|
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`DEBUG - createNote - Exception: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
@ -537,10 +556,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
try {
|
try {
|
||||||
const args = CreateNoteSchema.parse(request.params.arguments);
|
const args = CreateNoteSchema.parse(request.params.arguments);
|
||||||
const { project_id, noteable_type, noteable_iid, body } = args;
|
const { project_id, noteable_type, noteable_iid, body } = args;
|
||||||
const note = await createNote(project_id, noteable_type, noteable_iid, body);
|
// Debug info that will be included in the response
|
||||||
return {
|
const debugInfo = {
|
||||||
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
gitlab_api_url: GITLAB_API_URL,
|
||||||
|
project_id,
|
||||||
|
noteable_type,
|
||||||
|
noteable_iid,
|
||||||
|
constructed_url: `${GITLAB_API_URL}/projects/${encodeURIComponent(project_id)}/${noteable_type}s/${noteable_iid}/notes`
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
|
const note = await createNote(project_id, noteable_type, noteable_iid, body);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
// Include debug info in the error message
|
||||||
|
throw new Error(`Error with debug info: ${JSON.stringify(debugInfo)}\n${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
@ -565,6 +598,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
async function runServer() {
|
async function runServer() {
|
||||||
|
console.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||||
|
console.error("!!! RUNNING VERSION 1.0.7-fix WITH URL DEBUGGING FIX !!!");
|
||||||
|
console.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
console.error("GitLab MCP Server running on stdio");
|
console.error("GitLab MCP Server running on stdio");
|
||||||
|
136
index.ts
136
index.ts
@ -11,6 +11,8 @@ import { z } from "zod";
|
|||||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { dirname, resolve } from "path";
|
import { dirname, resolve } from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
import {
|
import {
|
||||||
GitLabForkSchema,
|
GitLabForkSchema,
|
||||||
GitLabReferenceSchema,
|
GitLabReferenceSchema,
|
||||||
@ -54,10 +56,24 @@ import {
|
|||||||
CreateNoteSchema,
|
CreateNoteSchema,
|
||||||
} from "./schemas.js";
|
} from "./schemas.js";
|
||||||
|
|
||||||
|
// Read version from package.json
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
||||||
|
let SERVER_VERSION = "unknown";
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(packageJsonPath)) {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
SERVER_VERSION = packageJson.version || SERVER_VERSION;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Warning: Could not read version from package.json:", error);
|
||||||
|
}
|
||||||
|
|
||||||
const server = new Server(
|
const server = new Server(
|
||||||
{
|
{
|
||||||
name: "better-gitlab-mcp-server",
|
name: "better-gitlab-mcp-server",
|
||||||
version: "0.0.1",
|
version: SERVER_VERSION,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@ -67,8 +83,34 @@ const server = new Server(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
|
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
|
||||||
const GITLAB_API_URL =
|
|
||||||
process.env.GITLAB_API_URL || "https://gitlab.com/api/v4";
|
// Smart URL handling for GitLab API
|
||||||
|
function normalizeGitLabApiUrl(url?: string): string {
|
||||||
|
if (!url) {
|
||||||
|
return "https://gitlab.com/api/v4";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
let normalizedUrl = url.endsWith('/') ? url.slice(0, -1) : url;
|
||||||
|
|
||||||
|
// Check if URL already has /api/v4
|
||||||
|
if (!normalizedUrl.endsWith('/api/v4') && !normalizedUrl.endsWith('/api/v4/')) {
|
||||||
|
// Append /api/v4 if not already present
|
||||||
|
normalizedUrl = `${normalizedUrl}/api/v4`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the normalizeGitLabApiUrl function to handle various URL formats
|
||||||
|
const GITLAB_API_URL = normalizeGitLabApiUrl(process.env.GITLAB_API_URL || "");
|
||||||
|
|
||||||
|
// Add debug logging for API URL construction
|
||||||
|
console.log("=== MCP Server Configuration ===");
|
||||||
|
console.log(`GITLAB_API_URL = "${GITLAB_API_URL}"`);
|
||||||
|
console.log(`Example project API URL = "${GITLAB_API_URL}/projects/123"`);
|
||||||
|
console.log(`Example Notes API URL = "${GITLAB_API_URL}/projects/123/issues/1/notes"`);
|
||||||
|
console.log("===============================");
|
||||||
|
|
||||||
if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
|
if (!GITLAB_PERSONAL_ACCESS_TOKEN) {
|
||||||
console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
|
console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set");
|
||||||
@ -101,7 +143,7 @@ async function forkProject(
|
|||||||
): Promise<GitLabFork> {
|
): Promise<GitLabFork> {
|
||||||
// API 엔드포인트 URL 생성
|
// API 엔드포인트 URL 생성
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(projectId)}/fork`
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
@ -129,7 +171,7 @@ async function createBranch(
|
|||||||
options: z.infer<typeof CreateBranchOptionsSchema>
|
options: z.infer<typeof CreateBranchOptionsSchema>
|
||||||
): Promise<GitLabReference> {
|
): Promise<GitLabReference> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/repository/branches`
|
)}/repository/branches`
|
||||||
);
|
);
|
||||||
@ -150,7 +192,7 @@ async function createBranch(
|
|||||||
// 프로젝트의 기본 브랜치 조회
|
// 프로젝트의 기본 브랜치 조회
|
||||||
async function getDefaultBranchRef(projectId: string): Promise<string> {
|
async function getDefaultBranchRef(projectId: string): Promise<string> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(projectId)}`
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await fetch(url.toString(), {
|
||||||
@ -176,7 +218,7 @@ async function getFileContents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/repository/files/${encodedPath}`
|
)}/repository/files/${encodedPath}`
|
||||||
);
|
);
|
||||||
@ -213,7 +255,7 @@ async function createIssue(
|
|||||||
options: z.infer<typeof CreateIssueOptionsSchema>
|
options: z.infer<typeof CreateIssueOptionsSchema>
|
||||||
): Promise<GitLabIssue> {
|
): Promise<GitLabIssue> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(projectId)}/issues`
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await fetch(url.toString(), {
|
||||||
@ -244,7 +286,7 @@ async function createMergeRequest(
|
|||||||
options: z.infer<typeof CreateMergeRequestOptionsSchema>
|
options: z.infer<typeof CreateMergeRequestOptionsSchema>
|
||||||
): Promise<GitLabMergeRequest> {
|
): Promise<GitLabMergeRequest> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/merge_requests`
|
)}/merge_requests`
|
||||||
);
|
);
|
||||||
@ -292,7 +334,7 @@ async function createOrUpdateFile(
|
|||||||
): Promise<GitLabCreateUpdateFileResponse> {
|
): Promise<GitLabCreateUpdateFileResponse> {
|
||||||
const encodedPath = encodeURIComponent(filePath);
|
const encodedPath = encodeURIComponent(filePath);
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/repository/files/${encodedPath}`
|
)}/repository/files/${encodedPath}`
|
||||||
);
|
);
|
||||||
@ -344,7 +386,7 @@ async function createTree(
|
|||||||
ref?: string
|
ref?: string
|
||||||
): Promise<GitLabTree> {
|
): Promise<GitLabTree> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/repository/tree`
|
)}/repository/tree`
|
||||||
);
|
);
|
||||||
@ -392,7 +434,7 @@ async function createCommit(
|
|||||||
actions: FileOperation[]
|
actions: FileOperation[]
|
||||||
): Promise<GitLabCommit> {
|
): Promise<GitLabCommit> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/repository/commits`
|
)}/repository/commits`
|
||||||
);
|
);
|
||||||
@ -437,7 +479,7 @@ async function searchProjects(
|
|||||||
page: number = 1,
|
page: number = 1,
|
||||||
perPage: number = 20
|
perPage: number = 20
|
||||||
): Promise<GitLabSearchResponse> {
|
): Promise<GitLabSearchResponse> {
|
||||||
const url = new URL(`${GITLAB_API_URL}/api/v4/projects`);
|
const url = new URL(`${GITLAB_API_URL}/projects`);
|
||||||
url.searchParams.append("search", query);
|
url.searchParams.append("search", query);
|
||||||
url.searchParams.append("page", page.toString());
|
url.searchParams.append("page", page.toString());
|
||||||
url.searchParams.append("per_page", perPage.toString());
|
url.searchParams.append("per_page", perPage.toString());
|
||||||
@ -477,7 +519,7 @@ async function searchProjects(
|
|||||||
async function createRepository(
|
async function createRepository(
|
||||||
options: z.infer<typeof CreateRepositoryOptionsSchema>
|
options: z.infer<typeof CreateRepositoryOptionsSchema>
|
||||||
): Promise<GitLabRepository> {
|
): Promise<GitLabRepository> {
|
||||||
const response = await fetch(`${GITLAB_API_URL}/api/v4/projects`, {
|
const response = await fetch(`${GITLAB_API_URL}/projects`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
@ -511,7 +553,7 @@ async function getMergeRequest(
|
|||||||
mergeRequestIid: number
|
mergeRequestIid: number
|
||||||
): Promise<GitLabMergeRequest> {
|
): Promise<GitLabMergeRequest> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/merge_requests/${mergeRequestIid}`
|
)}/merge_requests/${mergeRequestIid}`
|
||||||
);
|
);
|
||||||
@ -531,7 +573,7 @@ async function getMergeRequestDiffs(
|
|||||||
view?: "inline" | "parallel"
|
view?: "inline" | "parallel"
|
||||||
): Promise<GitLabMergeRequestDiff[]> {
|
): Promise<GitLabMergeRequestDiff[]> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/merge_requests/${mergeRequestIid}/changes`
|
)}/merge_requests/${mergeRequestIid}/changes`
|
||||||
);
|
);
|
||||||
@ -559,7 +601,7 @@ async function updateMergeRequest(
|
|||||||
>
|
>
|
||||||
): Promise<GitLabMergeRequest> {
|
): Promise<GitLabMergeRequest> {
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
`${GITLAB_API_URL}/projects/${encodeURIComponent(
|
||||||
projectId
|
projectId
|
||||||
)}/merge_requests/${mergeRequestIid}`
|
)}/merge_requests/${mergeRequestIid}`
|
||||||
);
|
);
|
||||||
@ -594,8 +636,14 @@ async function createNote(
|
|||||||
body: JSON.stringify({ body }),
|
body: JSON.stringify({ body }),
|
||||||
});
|
});
|
||||||
|
|
||||||
await handleGitLabError(response);
|
if (!response.ok) {
|
||||||
return await response.json(); // ⚙️ 응답 타입은 GitLab API 문서에 따라 조정 가능, 필요하면 스키마 정의
|
const errorText = await response.text();
|
||||||
|
throw new Error(
|
||||||
|
`GitLab API error: ${response.status} ${response.statusText}\n${errorText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
@ -828,28 +876,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "create_note": {
|
case "create_note": {
|
||||||
try {
|
const args = CreateNoteSchema.parse(request.params.arguments);
|
||||||
const args = CreateNoteSchema.parse(request.params.arguments);
|
const { project_id, noteable_type, noteable_iid, body } = args;
|
||||||
const { project_id, noteable_type, noteable_iid, body } = args;
|
|
||||||
const note = await createNote(
|
const note = await createNote(
|
||||||
project_id,
|
project_id,
|
||||||
noteable_type,
|
noteable_type,
|
||||||
noteable_iid,
|
noteable_iid,
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
content: [{ type: "text", text: JSON.stringify(note, null, 2) }],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof z.ZodError) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid arguments: ${error.errors
|
|
||||||
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
||||||
.join(", ")}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -868,9 +906,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function runServer() {
|
async function runServer() {
|
||||||
const transport = new StdioServerTransport();
|
try {
|
||||||
await server.connect(transport);
|
console.error("========================");
|
||||||
console.error("GitLab MCP Server running on stdio");
|
console.error(`GitLab MCP Server v${SERVER_VERSION}`);
|
||||||
|
console.error(`API URL: ${GITLAB_API_URL}`);
|
||||||
|
console.error("========================");
|
||||||
|
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error("GitLab MCP Server running on stdio");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing server:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runServer().catch((error) => {
|
runServer().catch((error) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@zereight/mcp-gitlab",
|
"name": "@zereight/mcp-gitlab",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7-fix",
|
||||||
"description": "MCP server for using the GitLab API",
|
"description": "MCP server for using the GitLab API",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "zereight",
|
"author": "zereight",
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.6.2"
|
"@types/node": "^22.13.10",
|
||||||
|
"typescript": "^5.8.2",
|
||||||
|
"zod": "3.21.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
test-note.ts
Normal file
66
test-note.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* This test file verifies that the createNote function works correctly
|
||||||
|
* with the fixed endpoint URL construction that uses plural resource names
|
||||||
|
* (issues instead of issue, merge_requests instead of merge_request).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
|
// GitLab API configuration (replace with actual values when testing)
|
||||||
|
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
|
||||||
|
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_TOKEN || "";
|
||||||
|
const PROJECT_ID = process.env.PROJECT_ID || "your/project";
|
||||||
|
const ISSUE_IID = Number(process.env.ISSUE_IID || "1");
|
||||||
|
|
||||||
|
async function testCreateIssueNote() {
|
||||||
|
try {
|
||||||
|
// Using plural form "issues" in the URL
|
||||||
|
const url = new URL(
|
||||||
|
`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(
|
||||||
|
PROJECT_ID
|
||||||
|
)}/issues/${ISSUE_IID}/notes`
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ body: "Test note from API - with plural endpoint" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorBody = await response.text();
|
||||||
|
throw new Error(
|
||||||
|
`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Successfully created note:");
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating note:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only run the test if executed directly
|
||||||
|
if (require.main === module) {
|
||||||
|
console.log("Testing note creation with plural 'issues' endpoint...");
|
||||||
|
testCreateIssueNote().then(success => {
|
||||||
|
if (success) {
|
||||||
|
console.log("✅ Test successful!");
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
console.log("❌ Test failed!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other tests
|
||||||
|
export { testCreateIssueNote };
|
Reference in New Issue
Block a user