/**
* @fileoverview Tool registration module for DeepSource MCP tools
*
* This module handles the registration of all DeepSource tools with the
* MCP server. It centralizes tool registration logic and provides a
* clean interface for registering tools.
*
* @packageDocumentation
*/
import { ToolRegistry, ToolDefinition } from './tool-registry.js';
import { toolSchemas } from './tool-definitions.js';
import {
handleProjects,
handleDeepsourceQualityMetrics,
handleDeepsourceUpdateMetricThreshold,
handleDeepsourceUpdateMetricSetting,
handleDeepsourceComplianceReport,
handleDeepsourceProjectIssues,
handleDeepsourceProjectRuns,
handleDeepsourceRun,
handleDeepsourceRecentRunIssues,
handleDeepsourceDependencyVulnerabilities,
} from '../handlers/index.js';
import { DeepsourceRunParams } from '../handlers/run.js';
import { DeepsourceRecentRunIssuesParams } from '../handlers/recent-run-issues.js';
import { DeepsourceDependencyVulnerabilitiesParams } from '../handlers/dependency-vulnerabilities.js';
import { MetricKey } from '../types/metrics.js';
import { AnalyzerShortcode } from '../types/branded.js';
import { MetricShortcode } from '../models/metrics.js';
import { ReportType } from '../types/report-types.js';
import { createLogger } from '../utils/logging/logger.js';
const logger = createLogger('ToolRegistration');
/**
* Handler mapping for tool schemas
*/
const TOOL_HANDLERS: Record<string, (params: unknown) => Promise<unknown>> = {
projects: async (params: unknown) => {
logger.info('=== PROJECTS HANDLER START ===');
logger.info('Projects handler called with params:', { params, type: typeof params });
try {
// Params currently unused but may be used for future filtering
const result = await handleProjects();
logger.info('=== PROJECTS HANDLER SUCCESS ===', { result });
return result;
} catch (error) {
logger.error('=== PROJECTS HANDLER ERROR ===', {
error,
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
},
quality_metrics: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const metricsParams: { projectKey: string; shortcodeIn?: MetricShortcode[] } = {
projectKey: typedParams.projectKey as string,
};
const shortcodeIn = typedParams.shortcodeIn as MetricShortcode[] | undefined;
if (shortcodeIn) {
metricsParams.shortcodeIn = shortcodeIn;
}
return handleDeepsourceQualityMetrics(metricsParams);
},
update_metric_threshold: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const thresholdParams: {
projectKey: string;
repositoryId: string;
metricShortcode: MetricShortcode;
metricKey: MetricKey;
thresholdValue?: number | null;
} = {
projectKey: typedParams.projectKey as string,
repositoryId: typedParams.repositoryId as string,
metricShortcode: typedParams.metricShortcode as MetricShortcode,
metricKey: typedParams.metricKey as MetricKey,
};
const thresholdValue = typedParams.thresholdValue as number | null | undefined;
if (thresholdValue !== undefined) {
thresholdParams.thresholdValue = thresholdValue;
}
return handleDeepsourceUpdateMetricThreshold(thresholdParams);
},
update_metric_setting: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
return handleDeepsourceUpdateMetricSetting({
projectKey: typedParams.projectKey as string,
repositoryId: typedParams.repositoryId as string,
metricShortcode: typedParams.metricShortcode as MetricShortcode,
isReported: typedParams.isReported as boolean,
isThresholdEnforced: typedParams.isThresholdEnforced as boolean,
});
},
compliance_report: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
return handleDeepsourceComplianceReport({
projectKey: typedParams.projectKey as string,
reportType: typedParams.reportType as ReportType,
});
},
project_issues: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const issuesParams: {
projectKey: string;
path?: string;
analyzerIn?: string[];
tags?: string[];
first?: number;
last?: number;
after?: string;
before?: string;
} = {
projectKey: typedParams.projectKey as string,
};
if (typeof typedParams.path === 'string') {
issuesParams.path = typedParams.path;
}
if (Array.isArray(typedParams.analyzerIn)) {
issuesParams.analyzerIn = typedParams.analyzerIn as string[];
}
if (Array.isArray(typedParams.tags)) {
issuesParams.tags = typedParams.tags as string[];
}
if (typeof typedParams.first === 'number') {
issuesParams.first = typedParams.first;
}
if (typeof typedParams.last === 'number') {
issuesParams.last = typedParams.last;
}
if (typeof typedParams.after === 'string') {
issuesParams.after = typedParams.after;
}
if (typeof typedParams.before === 'string') {
issuesParams.before = typedParams.before;
}
return handleDeepsourceProjectIssues(issuesParams);
},
runs: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const runsParams: {
projectKey: string;
analyzerIn: AnalyzerShortcode[];
first?: number;
last?: number;
after?: string;
before?: string;
} = {
projectKey: typedParams.projectKey as string,
analyzerIn: typedParams.analyzerIn as AnalyzerShortcode[],
};
if (typeof typedParams.first === 'number') {
runsParams.first = typedParams.first;
}
if (typeof typedParams.last === 'number') {
runsParams.last = typedParams.last;
}
if (typeof typedParams.after === 'string') {
runsParams.after = typedParams.after;
}
if (typeof typedParams.before === 'string') {
runsParams.before = typedParams.before;
}
return handleDeepsourceProjectRuns(runsParams);
},
run: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const runParams: DeepsourceRunParams = {
projectKey: typedParams.projectKey as string,
runIdentifier: typedParams.runIdentifier as string,
};
if (typeof typedParams.isCommitOid === 'boolean') {
runParams.isCommitOid = typedParams.isCommitOid;
}
return handleDeepsourceRun(runParams);
},
recent_run_issues: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const recentRunParams: DeepsourceRecentRunIssuesParams = {
projectKey: typedParams.projectKey as string,
branchName: typedParams.branchName as string,
};
if (typeof typedParams.first === 'number') {
recentRunParams.first = typedParams.first;
}
if (typeof typedParams.last === 'number') {
recentRunParams.last = typedParams.last;
}
if (typeof typedParams.after === 'string') {
recentRunParams.after = typedParams.after;
}
if (typeof typedParams.before === 'string') {
recentRunParams.before = typedParams.before;
}
return handleDeepsourceRecentRunIssues(recentRunParams);
},
dependency_vulnerabilities: async (params: unknown) => {
const typedParams = params as Record<string, unknown>;
const vulnParams: DeepsourceDependencyVulnerabilitiesParams = {
projectKey: typedParams.projectKey as string,
};
if (typeof typedParams.first === 'number') {
vulnParams.first = typedParams.first;
}
if (typeof typedParams.last === 'number') {
vulnParams.last = typedParams.last;
}
if (typeof typedParams.after === 'string') {
vulnParams.after = typedParams.after;
}
if (typeof typedParams.before === 'string') {
vulnParams.before = typedParams.before;
}
return handleDeepsourceDependencyVulnerabilities(vulnParams);
},
};
/**
* Creates a tool definition from a schema and handler
*
* @param schema - The tool schema
* @param handler - The tool handler function
* @returns The tool definition
*/
function createToolDefinition(
schema: {
name: string;
description: string;
inputSchema?: unknown;
outputSchema?: unknown;
},
handler: (_params: unknown) => Promise<unknown>
): ToolDefinition {
logger.debug(`Creating tool definition for ${schema.name}`, {
hasInputSchema: Boolean(schema.inputSchema),
inputSchemaType: typeof schema.inputSchema,
inputSchema: schema.inputSchema,
});
return {
name: schema.name,
description: schema.description,
inputSchema: schema.inputSchema,
outputSchema: schema.outputSchema,
handler,
} as ToolDefinition;
}
/**
* Registers all DeepSource tools with the tool registry
*
* @param registry - The tool registry to register tools with
*/
export function registerDeepSourceTools(registry: ToolRegistry): void {
logger.info('=== REGISTER DEEPSOURCE TOOLS START ===');
logger.info('Registering DeepSource tools', {
toolSchemasType: typeof toolSchemas,
toolSchemasIsArray: Array.isArray(toolSchemas),
toolSchemasLength: Array.isArray(toolSchemas) ? toolSchemas.length : 'not an array',
toolSchemaNames: Array.isArray(toolSchemas) ? toolSchemas.map((s) => s.name) : 'not an array',
});
const toolDefinitions: ToolDefinition[] = [];
// Create tool definitions from schemas and handlers
for (const schema of toolSchemas) {
logger.debug(`Processing schema: ${schema.name}`);
const handler = TOOL_HANDLERS[schema.name];
if (!handler) {
logger.warn(`No handler found for tool: ${schema.name}`);
continue;
}
logger.debug(`Creating tool definition for: ${schema.name}`);
const toolDef = createToolDefinition(schema, handler);
toolDefinitions.push(toolDef);
logger.debug(`Successfully created tool definition for: ${schema.name}`);
}
logger.info(`Prepared ${toolDefinitions.length} tool definitions for registration`);
// Register all tools
registry.registerTools(toolDefinitions);
logger.info('=== REGISTER DEEPSOURCE TOOLS COMPLETE ===', {
registeredCount: toolDefinitions.length,
registeredTools: toolDefinitions.map((t) => t.name),
});
}
/**
* Tool categories for organization
*/
export enum ToolCategory {
PROJECT_MANAGEMENT = 'project_management',
CODE_QUALITY = 'code_quality',
SECURITY = 'security',
ANALYSIS = 'analysis',
DEPENDENCIES = 'dependencies',
}
/**
* Tool metadata for categorization and filtering
*/
export interface ToolMetadata {
category: ToolCategory;
tags: string[];
requiresAuth: boolean;
supportsFiltering: boolean;
supportsPagination: boolean;
}
/**
* Metadata for all DeepSource tools
*/
export const TOOL_METADATA: Record<string, ToolMetadata> = {
projects: {
category: ToolCategory.PROJECT_MANAGEMENT,
tags: ['projects', 'list', 'discovery'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: false,
},
quality_metrics: {
category: ToolCategory.CODE_QUALITY,
tags: ['metrics', 'quality', 'code', 'statistics'],
requiresAuth: true,
supportsFiltering: true,
supportsPagination: false,
},
update_metric_threshold: {
category: ToolCategory.CODE_QUALITY,
tags: ['metrics', 'quality', 'threshold', 'update', 'mutation'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: false,
},
update_metric_setting: {
category: ToolCategory.CODE_QUALITY,
tags: ['metrics', 'quality', 'settings', 'update', 'mutation'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: false,
},
compliance_report: {
category: ToolCategory.SECURITY,
tags: ['compliance', 'security', 'report', 'owasp', 'sans'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: false,
},
project_issues: {
category: ToolCategory.CODE_QUALITY,
tags: ['issues', 'problems', 'code', 'quality', 'list'],
requiresAuth: true,
supportsFiltering: true,
supportsPagination: true,
},
runs: {
category: ToolCategory.ANALYSIS,
tags: ['runs', 'analysis', 'history', 'list'],
requiresAuth: true,
supportsFiltering: true,
supportsPagination: true,
},
run: {
category: ToolCategory.ANALYSIS,
tags: ['run', 'analysis', 'details', 'single'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: false,
},
recent_run_issues: {
category: ToolCategory.ANALYSIS,
tags: ['issues', 'run', 'recent', 'branch', 'analysis'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: true,
},
dependency_vulnerabilities: {
category: ToolCategory.DEPENDENCIES,
tags: ['dependencies', 'vulnerabilities', 'security', 'cve', 'list'],
requiresAuth: true,
supportsFiltering: false,
supportsPagination: true,
},
};
/**
* Gets tools by category
*
* @param category - The tool category
* @returns Array of tool names in the category
*/
export function getToolsByCategory(category: ToolCategory): string[] {
return Object.entries(TOOL_METADATA)
.filter(([, metadata]) => metadata.category === category)
.map(([name]) => name);
}
/**
* Gets tools by tag
*
* @param tag - The tag to filter by
* @returns Array of tool names with the tag
*/
export function getToolsByTag(tag: string): string[] {
return Object.entries(TOOL_METADATA)
.filter(([, metadata]) => metadata.tags.includes(tag))
.map(([name]) => name);
}
/**
* Gets paginated tools
*
* @returns Array of tool names that support pagination
*/
export function getPaginatedTools(): string[] {
return Object.entries(TOOL_METADATA)
.filter(([, metadata]) => metadata.supportsPagination)
.map(([name]) => name);
}