Skip to content

Commit 8eb1016

Browse files
committed
feat: add health check tool
1 parent c5397e5 commit 8eb1016

5 files changed

Lines changed: 54 additions & 4 deletions

File tree

index.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ import {
373373
ListWebhooksSchema,
374374
ListWebhookEventsSchema,
375375
GetWebhookEventSchema,
376+
HealthCheckSchema,
376377
} from "./schemas.js";
377378

378379
import { randomUUID } from "node:crypto";
@@ -1254,6 +1255,15 @@ function buildAuthHeaders(): Record<string, string> {
12541255
return {};
12551256
}
12561257

1258+
function usesJobTokenHeader(): boolean {
1259+
if (GITLAB_JOB_TOKEN) return true;
1260+
if (REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH) {
1261+
const ctx = sessionAuthStore.getStore();
1262+
return ctx?.header === "JOB-TOKEN";
1263+
}
1264+
return false;
1265+
}
1266+
12571267
/**
12581268
* Get the effective GitLab API URL for the current request
12591269
* In REMOTE_AUTHORIZATION mode with ENABLE_DYNAMIC_API_URL, reads from session context
@@ -7623,9 +7633,23 @@ async function createCommitStatus(
76237633
async function getCurrentUser(): Promise<GitLabUser> {
76247634
const response = await fetch(`${getEffectiveApiUrl()}/user`, getFetchConfig());
76257635

7636+
if (response.ok) {
7637+
const data = await response.json();
7638+
return GitLabUserSchema.parse(data);
7639+
}
7640+
7641+
if ((response.status === 401 || response.status === 403) && usesJobTokenHeader()) {
7642+
const jobResponse = await fetch(`${getEffectiveApiUrl()}/job`, getFetchConfig());
7643+
if (jobResponse.ok) {
7644+
const jobData = await jobResponse.json() as { user?: { username?: string; id?: number; name?: string } };
7645+
if (jobData.user) {
7646+
return GitLabUserSchema.parse(jobData.user);
7647+
}
7648+
}
7649+
}
7650+
76267651
await handleGitLabError(response);
7627-
const data = await response.json();
7628-
return GitLabUserSchema.parse(data);
7652+
throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
76297653
}
76307654

76317655
/**
@@ -10341,6 +10365,21 @@ async function handleToolCall(params: any) {
1034110365
};
1034210366
}
1034310367

10368+
case "health_check": {
10369+
HealthCheckSchema.parse(params.arguments);
10370+
const url = new URL(`${getEffectiveApiUrl()}/user`);
10371+
const response = await fetch(url.toString(), getFetchConfig());
10372+
let authenticated = response.ok;
10373+
if (!authenticated && (response.status === 401 || response.status === 403) && (GITLAB_JOB_TOKEN || usesJobTokenHeader())) {
10374+
const jobUrl = new URL(`${getEffectiveApiUrl()}/job`);
10375+
const jobResponse = await fetch(jobUrl.toString(), getFetchConfig());
10376+
authenticated = jobResponse.ok;
10377+
}
10378+
return {
10379+
content: [{ type: "text", text: JSON.stringify({ status: authenticated ? "ok" : "error", authenticated, gitlab_url: getEffectiveApiUrl() }) }],
10380+
};
10381+
}
10382+
1034410383
default:
1034510384
throw new Error(`Unknown tool: ${params.name}`);
1034610385
}

schemas.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3738,3 +3738,5 @@ export type GitLabSearchBlobResult = z.infer<typeof GitLabSearchBlobResultSchema
37383738
export type SearchCodeOptions = z.infer<typeof SearchCodeSchema>;
37393739
export type SearchProjectCodeOptions = z.infer<typeof SearchProjectCodeSchema>;
37403740
export type SearchGroupCodeOptions = z.infer<typeof SearchGroupCodeSchema>;
3741+
3742+
export const HealthCheckSchema = z.object({});

test/test-toolset-filtering.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const TOOLSET_TOOL_COUNTS: Record<string, number> = {
3434
issues: 23,
3535
repositories: 7,
3636
branches: 6,
37-
projects: 8,
37+
projects: 9,
3838
labels: 5,
3939
ci: 2,
4040
pipelines: 19,

test/utils/server-launcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,12 @@ export async function launchServer(config: ServerConfig): Promise<ServerInstance
110110
serverProcess.kill("SIGTERM");
111111

112112
// Force kill if not terminated within 5 seconds
113-
setTimeout(() => {
113+
const forceKillTimer = setTimeout(() => {
114114
if (!serverProcess.killed) {
115115
serverProcess.kill("SIGKILL");
116116
}
117117
}, 5000);
118+
forceKillTimer.unref();
118119
}
119120
},
120121
};

tools/registry.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
EditProjectMilestoneSchema,
7575
ExecuteGraphQLSchema,
7676
ForkRepositorySchema,
77+
HealthCheckSchema,
7778
GetBranchDiffsSchema,
7879
GetCommitDiffSchema,
7980
GetCommitSchema,
@@ -849,6 +850,11 @@ export const allTools = [
849850
description: "Download an uploaded file from a project (images returned as base64; use local_path to save to disk)",
850851
inputSchema: toJSONSchema(DownloadAttachmentSchema),
851852
},
853+
{
854+
name: "health_check",
855+
description: "Verify server status and authentication",
856+
inputSchema: toJSONSchema(HealthCheckSchema),
857+
},
852858
{
853859
name: "list_events",
854860
description: "List events for the authenticated user (before/after: YYYY-MM-DD)",
@@ -1063,6 +1069,7 @@ export const allTools = [
10631069
// Define which tools are read-only
10641070
export const readOnlyTools = new Set([
10651071
"discover_tools",
1072+
"health_check",
10661073
"search_repositories",
10671074
"search_code",
10681075
"search_project_code",
@@ -1374,6 +1381,7 @@ export const TOOLSET_DEFINITIONS: readonly ToolsetDefinition[] = [
13741381
"verify_namespace",
13751382
"list_group_projects",
13761383
"list_group_iterations",
1384+
"health_check",
13771385
]),
13781386
},
13791387
{

0 commit comments

Comments
 (0)