Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions .eslintrc.json

This file was deleted.

140 changes: 139 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,142 @@ docs/tools/*.md
docs/reference/tool-coverage.md
# MkDocs build output
site/
.venv-docs/
.venv-docs/

# Not yet formatted with Prettier. Remove entries as files are reformatted.
/.github/agents/
/.github/skills/
/mcp-server/src/
/.github/copilot-instructions.md
/.github/prompts/quick-fix.prompt.md
/.github/prompts/quick-plan.prompt.md
/.well-known/mcp/server-card.json
/CHANGELOG.md
/cli-command.ts
/cli/args.ts
/cli/compact-mcp-result.ts
/cli/curated.ts
/cli/help.ts
/cli/output.ts
/cli/router.ts
/cli/tables.ts
/customSchemas.ts
/docker/DOCKERHUB.md
/docker/README.md
/docs/auth/oauth-callback-proxy.md
/docs/clients/vscode.md
/docs/comparison/community-gitlab-mcp-a.md
/docs/comparison/index.md
/docs/configuration/dynamic-api-url.md
/docs/configuration/multi-instance-design.md
/docs/configuration/stateless-mode.md
/docs/features/resolve-issue-thread.md
/docs/getting-started/cli-arguments.md
/docs/getting-started/cli-without-mcp.md
/docs/reference/cli-usage-design.md
/docs/reference/dependency-proxy-design.md
/docs/reference/github-pages-setup.md
/event.json
/oauth-device-flow.ts
/oauth-proxy.ts
/oauth.ts
/PR_SUMMARY.md
/README.ko.md
/README.md
/README.pt-BR.md
/README.zh-CN.md
/schemas.ts
/scripts/check-skill-sync.ts
/server.json
/server/metrics.ts
/skills/gitlab-mcp/reference/issues.md
/skills/gitlab-mcp/SKILL.md
/skills/gl-cli/SKILL.md
/stateless/client-id.ts
/stateless/consumed-proxy-code-cache.ts
/stateless/errors.ts
/stateless/index.ts
/stateless/pending-auth.ts
/stateless/secret.ts
/stateless/session-id.ts
/stateless/types.ts
/test-cli.js
/test-note.ts
/test-resolve-issue-note.ts
/test/callback-proxy-tests.ts
/test/cli/compact-mcp-result.test.ts
/test/cli/credentials.test.ts
/test/cli/output.test.ts
/test/cli/replay-command.test.ts
/test/cli/router.test.ts
/test/client-pool-test.ts
/test/clients/client.ts
/test/clients/custom-header-client.ts
/test/clients/sse-client.ts
/test/clients/stdio-client.ts
/test/clients/streamable-http-client.ts
/test/config-allowed-groups.test.ts
/test/create-commit-actions.test.ts
/test/dynamic-api-url-test.ts
/test/dynamic-routing-tests.ts
/test/gitlab-artifact-entry-schema.test.ts
/test/mcp-oauth-tests.ts
/test/multi-server-test.ts
/test/no-proxy-integration-test.ts
/test/no-proxy-test.ts
/test/oauth-scopes.test.ts
/test/oauth-startup.test.ts
/test/oauth-tests.ts
/test/remote-auth-simple-test.ts
/test/remote-auth-tests.ts
/test/schema-tests.ts
/test/stateless/callback-proxy.test.ts
/test/stateless/client-id.test.ts
/test/stateless/codec.test.ts
/test/stateless/config-ttl.test.ts
/test/stateless/consumed-proxy-code-cache.test.ts
/test/stateless/session-id-integration.test.ts
/test/streamable-http-sse-stream.test.ts
/test/streamable-http-unauthenticated-discovery.test.ts
/test/test-all-transport-server.ts
/test/test-blank-filters.ts
/test/test-ci-variables.ts
/test/test-dependency-proxy.ts
/test/test-deployment-tools.ts
/test/test-download-attachment.ts
/test/test-empty-toolsets.ts
/test/test-get-file-blame.ts
/test/test-geteffectiveprojectid.ts
/test/test-issue-description-patch.ts
/test/test-job-artifacts.ts
/test/test-json-schema.ts
/test/test-list-group-members.ts
/test/test-list-merge-requests.ts
/test/test-list-project-members.ts
/test/test-merge-request-approval-state-tools.ts
/test/test-merge-request-pipelines.ts
/test/test-mr-diffs-filter.ts
/test/test-mr-file-diffs.ts
/test/test-remote-downloads.ts
/test/test-search-code.ts
/test/test-snippets.ts
/test/test-tags.ts
/test/test-todos.ts
/test/test-token-optimizations.ts
/test/test-toolset-filtering.ts
/test/test-upload-markdown.ts
/test/utils/bulk-publish-options.test.ts
/test/utils/forwarded-public-base-url.test.ts
/test/utils/graphql-query.test.ts
/test/utils/mock-gitlab-server.ts
/test/utils/redact-url.test.ts
/test/utils/tool-args.test.ts
/test/utils/version-check.test.ts
/utils/forwarded-public-base-url.ts
/utils/gitlab-instance-version.ts
/utils/helpers.ts
/utils/merge-request-position.ts
/utils/patch-helper.ts
/utils/proxy-client-ip.ts
/utils/schema.ts
/utils/version-check.ts
35 changes: 35 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import js from "@eslint/js";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";

const tsFiles = ["**/*.ts"];

export default [
{ ignores: ["node_modules/", "build/", "coverage/", "**/*.js", "**/*.mjs", "**/*.cjs"] },
{ ...js.configs.recommended, files: tsFiles },
...tseslint.configs["flat/recommended"].map(config => ({ ...config, files: tsFiles })),
{
files: tsFiles,
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"no-console": "warn",
"prefer-const": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", ignoreRestSiblings: true },
],
"@typescript-eslint/no-empty-object-type": [
"error",
{ allowInterfaces: "with-single-extends" },
],
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-non-null-assertion": "warn",
},
},
];
31 changes: 3 additions & 28 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,6 @@
UpdateIssueNoteSchema,
UpdateIssueSchema,
UpdateIssueDescriptionPatchSchema,
type UpdateIssueDescriptionPatchOptions,
UpdateLabelSchema,
UpdateProjectSchema,
UpdateMergeRequestNoteSchema,
Expand Down Expand Up @@ -712,14 +711,7 @@
HealthCheckSchema,
} from "./schemas.js";

import {
randomUUID,
createCipheriv,
createDecipheriv,
randomBytes,
createHash,
timingSafeEqual,
} from "node:crypto";
import { randomUUID, timingSafeEqual } from "node:crypto";
import { createLogger, flushBufferedLogs, flushStartupLogs } from "./utils/logger.js";

const logger = createLogger();
Expand Down Expand Up @@ -861,7 +853,7 @@
})),
}));

mcpServer.server.setRequestHandler(GetPromptRequestSchema, async (request: any) => {

Check warning on line 856 in index.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
const prompt = MCP_PROMPTS.find(p => p.name === request.params.name);
if (!prompt) {
throw new Error(`Unknown prompt: ${request.params.name}`);
Expand All @@ -887,7 +879,7 @@
};
});

mcpServer.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {

Check warning on line 882 in index.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
// Manually retrieve the session context using the session ID passed in the request.
// This is a robust workaround for AsyncLocalStorage context loss.
const sessionId = request.params.sessionId;
Expand All @@ -898,7 +890,7 @@
request.params.arguments as Record<string, unknown> | undefined
);

const logCompletion = (result: any, completionOptions?: { skipJmespath?: boolean }) => {

Check warning on line 893 in index.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
const durationMs = Date.now() - start;
logger.info(
{ tool: toolName, event: "tool_call_done", durationMs },
Expand All @@ -910,7 +902,7 @@
(toolName === "download_release_asset" && !IS_REMOTE);
const filtered = applyJmespathToToolResult(result, jmespathExpression, {
...completionOptions,
maskValue: maskingEngine ? (value: unknown) => maskingEngine!.maskValue(value) : undefined,

Check warning on line 905 in index.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
});
const masked = maskingEngine
? maskingEngine.maskToolResult(filtered, {
Expand Down Expand Up @@ -1225,23 +1217,6 @@
return mcpServer;
}

/**
* Validate configuration at startup
*/
function isLoopbackBindHost(host: string): boolean {
const normalized = host
.trim()
.toLowerCase()
.replace(/^\[|\]$/g, "");
const isIpv4Loopback = /^127(?:\.\d{1,3}){3}$/.test(normalized);
return (
normalized === "localhost" ||
isIpv4Loopback ||
normalized === "::1" ||
normalized === "0:0:0:0:0:0:0:1"
);
}

function formatHostWithPort(host: string, port: number): string | null {
const normalized = host
.trim()
Expand Down Expand Up @@ -1788,7 +1763,7 @@
function defaultAuthRetryConfig() {
return {
isOAuthEnabled: () => USE_OAUTH && oauthClient != null,
refreshToken: (force: boolean) => oauthClient!.getAccessToken(force),

Check warning on line 1766 in index.ts

View workflow job for this annotation

GitHub Actions / test

Forbidden non-null assertion
onTokenRefreshed: (token: string) => {
OAUTH_ACCESS_TOKEN = token;
},
Expand Down Expand Up @@ -2656,7 +2631,7 @@
);

// Build request body, converting labels array to comma-separated string
const body: Record<string, any> = { ...options };

Check warning on line 2634 in index.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
if (body.labels && Array.isArray(body.labels)) {
body.labels = body.labels.join(",");
}
Expand Down Expand Up @@ -2904,7 +2879,7 @@
);

// Convert labels array to comma-separated string if present
const body: Record<string, any> = { ...options };

Check warning on line 2882 in index.ts

View workflow job for this annotation

GitHub Actions / test

Unexpected any. Specify a different type
if (body.labels && Array.isArray(body.labels)) {
body.labels = body.labels.join(",");
}
Expand Down Expand Up @@ -9783,7 +9758,7 @@
projectId = decodeURIComponent(projectId);
const baseUrl = `${getEffectiveApiUrl()}/projects/${encodeGitLabPathSegment(getEffectiveProjectId(projectId))}/repository/commits/${encodeGitLabPathSegment(sha)}/diff`;

let allDiffs: GitLabMergeRequestDiff[] = [];
const allDiffs: GitLabMergeRequestDiff[] = [];
let page = 1;

while (true) {
Expand Down Expand Up @@ -9977,7 +9952,7 @@
}

// Use listIssues with assignee_username filter
let listIssuesOptions: Omit<z.infer<typeof ListIssuesSchema>, "project_id"> = {
const listIssuesOptions: Omit<z.infer<typeof ListIssuesSchema>, "project_id"> = {
state: options.state || "opened", // Default to "opened" if not specified
labels: options.labels,
milestone: options.milestone,
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/src/prd-tools.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import * as path from "node:path";
import { getWorkspaceRoot, ensureDir, safeReadFile, safeWriteFile, safeJsonParse, generateId, errorResponse } from "./utils.js";
import { getWorkspaceRoot, ensureDir, safeReadFile, safeWriteFile, safeJsonParse, generateId } from "./utils.js";

function getPrdPath(): string {
return path.join(getWorkspaceRoot(), ".omc", "prd.json");
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
"test:oauth": "tsx test/oauth-tests.ts && node --import tsx/esm --test --experimental-test-isolation=none test/oauth-device-flow-tests.ts",
"test:list-merge-requests": "npm run build && tsx test/test-list-merge-requests.ts",
"test:approvals": "npm run build && tsx test/test-merge-request-approvals.ts",
"lint": "eslint . --ext .ts",
"lint": "eslint .",
"check:runtime-deps": "node scripts/check-runtime-deps.mjs",
"check:skill-sync": "tsx scripts/check-skill-sync.ts",
"docs:tools": "tsx scripts/generate-tool-docs.ts && tsx scripts/generate-tool-coverage.ts",
"lint:fix": "eslint . --ext .ts --fix",
"lint:fix": "eslint . --fix",
"release": "bash scripts/release.sh",
"release:mcp-registry": "bash scripts/publish_mcp_registry.sh",
"format": "prettier --write \"**/*.{js,ts,json,md}\"",
Expand All @@ -90,6 +90,7 @@
"zod": "^4.6.5"
},
"devDependencies": {
"@eslint/js": "^9.27.0",
"@types/express": "^5.0.2",
"@types/jmespath": "^0.15.2",
"@types/node": "^22.13.10",
Expand Down
3 changes: 1 addition & 2 deletions test/callback-proxy-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
} from "./utils/server-launcher.js";
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";

const MOCK_GITLAB_PORT_BASE = 9400;
const MCP_SERVER_PORT_BASE = 3400;
const MOCK_ACCESS_TOKEN = "ya29.mock-callback-proxy-token-123456";
const MOCK_REFRESH_TOKEN = "mock-refresh-token-abcdef";
Expand Down Expand Up @@ -129,7 +128,7 @@ describe("Callback Proxy Mode", () => {
let mcpBaseUrl: string;
let mockGitLab: MockGitLabServer;
let mockGitLabUrl: string;
let servers: ServerInstance[] = [];
const servers: ServerInstance[] = [];
const clientRedirectUri = "http://localhost:19999/oauth/callback";

// Track the last code+verifier GitLab received so we can verify the proxy PKCE
Expand Down
3 changes: 1 addition & 2 deletions test/client-pool-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const MOCK_TOKEN = 'glpat-mock-token-12345';
const POOL_MAX_SIZE = 2;

// Port ranges
const MOCK_GITLAB_PORT_BASE = 9500;
const MCP_SERVER_PORT_BASE = 3500;

console.log('🏊 Client Pool Test Suite');
Expand All @@ -30,7 +29,7 @@ console.log('');
describe('Client Pool Limits', () => {
let mcpUrl: string;
let mockGitLab: MockGitLabServer;
let servers: ServerInstance[] = [];
const servers: ServerInstance[] = [];
let mockGitLabUrl: string;

before(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/clients/custom-header-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class CustomHeaderClient {
if (this.transport) {
try {
await this.transport.close();
} catch (error) {
} catch {
// Silently ignore disconnect errors in tests
// In production, you'd want proper logging
} finally {
Expand Down
2 changes: 1 addition & 1 deletion test/clients/sse-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class SSETestClient implements MCPClientInterface {
try {
const tools = await this.listTools();
return Array.isArray(tools.tools) && tools.tools.length > 0;
} catch (error) {
} catch {
return false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/clients/stdio-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class StdioTestClient implements MCPClientInterface {
try {
const tools = await this.listTools();
return Array.isArray(tools.tools) && tools.tools.length > 0;
} catch (error) {
} catch {
return false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/clients/streamable-http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export class StreamableHTTPTestClient implements MCPClientInterface {
try {
const tools = await this.listTools();
return Array.isArray(tools.tools) && tools.tools.length > 0;
} catch (error) {
} catch {
return false;
}
}
Expand Down
Loading
Loading