import { DeepSourceClient } from '../deepsource.js';
import { MetricShortcode, MetricKey, MetricThresholdStatus } from '../types/metrics.js';
import { BaseHandlerDeps, HandlerFactory } from './base/handler.interface.js';
import {
createBaseHandlerFactory,
wrapInApiResponse,
createDefaultHandlerDeps,
} from './base/handler.factory.js';
import { createLogger, Logger } from '../utils/logging/logger.js';
import { IQualityMetricsRepository } from '../domain/aggregates/quality-metrics/quality-metrics.repository.js';
import { QualityMetrics } from '../domain/aggregates/quality-metrics/quality-metrics.aggregate.js';
import { RepositoryFactory } from '../infrastructure/factories/repository.factory.js';
import { asProjectKey } from '../types/branded.js';
import { MCPErrorFormatter, validateNonEmptyString } from '../utils/error-handling/index.js';
// Logger for the quality metrics handler
const logger = createLogger('QualityMetricsHandler');
/**
* Interface for parameters for fetching quality metrics
* @public
*/
export interface DeepsourceQualityMetricsParams {
/** DeepSource project key to fetch quality metrics for */
projectKey: string;
/** Optional filter for specific metric shortcodes */
shortcodeIn?: MetricShortcode[];
}
/**
* Interface for parameters for updating a metric threshold
* @public
*/
export interface DeepsourceUpdateMetricThresholdParams {
/** DeepSource project key to identify the project */
projectKey: string;
/** Repository GraphQL ID */
repositoryId: string;
/** Code for the metric to update */
metricShortcode: MetricShortcode;
/** Context key for the metric */
metricKey: MetricKey;
/** New threshold value, or null to remove */
thresholdValue?: number | null;
}
/**
* Interface for parameters for updating metric settings
* @public
*/
export interface DeepsourceUpdateMetricSettingParams {
/** DeepSource project key to identify the project */
projectKey: string;
/** Repository GraphQL ID */
repositoryId: string;
/** Code for the metric to update */
metricShortcode: MetricShortcode;
/** Whether the metric should be reported */
isReported: boolean;
/** Whether the threshold should be enforced */
isThresholdEnforced: boolean;
}
/**
* Extended dependencies interface for quality metrics handler
*/
interface QualityMetricsHandlerDeps {
qualityMetricsRepository: IQualityMetricsRepository;
logger: Logger;
}
/**
* Creates a quality metrics handler with injected dependencies
* @param deps - The dependencies for the handler
* @returns The configured handler function
*/
export function createQualityMetricsHandlerWithRepo(deps: QualityMetricsHandlerDeps) {
return async function handleQualityMetrics(params: DeepsourceQualityMetricsParams) {
try {
const { projectKey, shortcodeIn } = params;
// Validate required parameters using MCP error handling
validateNonEmptyString(projectKey, 'projectKey');
const projectKeyBranded = asProjectKey(projectKey);
deps.logger.info('Fetching quality metrics from repository', { projectKey, shortcodeIn });
// Get metrics from repository with server-side filtering if shortcodes specified
const filteredMetrics = shortcodeIn
? await deps.qualityMetricsRepository.findByProjectWithFilter(
projectKeyBranded,
shortcodeIn
)
: await deps.qualityMetricsRepository.findByProject(projectKeyBranded);
deps.logger.info('Successfully fetched quality metrics', {
count: filteredMetrics.length,
projectKey,
});
const metricsData = {
metrics: filteredMetrics.map((domainMetric: QualityMetrics) => ({
name: domainMetric.configuration.name,
shortcode: domainMetric.configuration.shortcode,
description: domainMetric.configuration.description,
positiveDirection: domainMetric.configuration.positiveDirection,
unit: domainMetric.configuration.unit,
minValueAllowed: domainMetric.configuration.minAllowed,
maxValueAllowed: domainMetric.configuration.maxAllowed,
isReported: domainMetric.configuration.isReported,
isThresholdEnforced: domainMetric.configuration.isThresholdEnforced,
items: [
{
id: domainMetric.repositoryId,
key: domainMetric.configuration.metricKey,
threshold: domainMetric.configuration.threshold?.value ?? null,
latestValue: domainMetric.currentValue?.value ?? null,
latestValueDisplay: domainMetric.currentValue?.displayValue ?? null,
thresholdStatus: domainMetric.thresholdStatus,
// Add helpful metadata for threshold values
thresholdInfo:
domainMetric.configuration.threshold &&
domainMetric.currentValue &&
domainMetric.configuration.threshold.value !== null &&
domainMetric.currentValue.value !== null
? {
difference:
domainMetric.currentValue.value -
domainMetric.configuration.threshold.value,
percentDifference:
domainMetric.configuration.threshold.value !== 0
? `${(((domainMetric.currentValue.value - domainMetric.configuration.threshold.value) / domainMetric.configuration.threshold.value) * 100).toFixed(2)}%`
: 'N/A',
isPassing: domainMetric.isCompliant,
}
: null,
},
],
})),
// Add helpful examples for threshold management
usage_examples: {
filtering:
'To filter metrics by type, use the shortcodeIn parameter with specific metric codes (e.g., ["LCV", "BCV"])',
updating_threshold: 'To update a threshold, use the update_metric_threshold tool',
updating_settings:
'To update metric settings (e.g., enable reporting or threshold enforcement), use the update_metric_setting tool',
},
};
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(metricsData),
},
],
};
} catch (error) {
deps.logger.error('Error in handleQualityMetrics', {
errorType: typeof error,
errorName: error instanceof Error ? error.name : 'Unknown',
errorMessage: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : 'No stack available',
});
// Use MCP-compliant error formatting
return MCPErrorFormatter.createErrorResponse(error, 'quality-metrics-fetch');
}
};
}
// Keep the old handler for backward compatibility with the base handler pattern
export const createQualityMetricsHandler: HandlerFactory<
BaseHandlerDeps,
DeepsourceQualityMetricsParams
> = createBaseHandlerFactory('quality_metrics', async (deps, { projectKey, shortcodeIn }) => {
const apiKey = deps.getApiKey();
deps.logger.debug('API key retrieved from config', {
length: apiKey.length,
prefix: `${apiKey.substring(0, 5)}...`,
});
const client = new DeepSourceClient(apiKey);
deps.logger.info('Fetching quality metrics', { projectKey, shortcodeIn });
const params: { shortcodeIn?: MetricShortcode[] } = {};
if (shortcodeIn !== undefined) {
params.shortcodeIn = shortcodeIn;
}
const metrics = await client.getQualityMetrics(projectKey, params);
deps.logger.info('Successfully fetched quality metrics', {
count: metrics.length,
projectKey,
});
const metricsData = {
metrics: metrics.map((metric) => ({
name: metric.name,
shortcode: metric.shortcode,
description: metric.description,
positiveDirection: metric.positiveDirection,
unit: metric.unit,
minValueAllowed: metric.minValueAllowed,
maxValueAllowed: metric.maxValueAllowed,
isReported: metric.isReported,
isThresholdEnforced: metric.isThresholdEnforced,
items: metric.items.map((item) => ({
id: item.id,
key: item.key,
threshold: item.threshold,
latestValue: item.latestValue,
latestValueDisplay: item.latestValueDisplay,
thresholdStatus: item.thresholdStatus,
// Add helpful metadata for threshold values
thresholdInfo:
item.threshold !== null &&
item.threshold !== undefined &&
item.latestValue !== null &&
item.latestValue !== undefined
? {
difference: item.latestValue - item.threshold,
percentDifference:
item.threshold !== 0
? `${(((item.latestValue - item.threshold) / item.threshold) * 100).toFixed(
2
)}%`
: 'N/A',
isPassing: item.thresholdStatus === MetricThresholdStatus.PASSING,
}
: null,
})),
})),
// Add helpful examples for threshold management
usage_examples: {
filtering:
'To filter metrics by type, use the shortcodeIn parameter with specific metric codes (e.g., ["LCV", "BCV"])',
updating_threshold: 'To update a threshold, use the update_metric_threshold tool',
updating_settings:
'To update metric settings (e.g., enable reporting or threshold enforcement), use the update_metric_setting tool',
},
};
return wrapInApiResponse(metricsData);
});
/**
* Fetches and returns quality metrics from a specified DeepSource project using domain aggregates
* @param params - Parameters for fetching metrics, including project key and optional filters
* @returns A response containing the metrics data with their values and thresholds
* @throws Error if the DEEPSOURCE_API_KEY environment variable is not set or if API call fails
* @public
*/
export async function handleDeepsourceQualityMetrics(params: DeepsourceQualityMetricsParams) {
try {
const baseDeps = createDefaultHandlerDeps({ logger });
const apiKey = baseDeps.getApiKey();
const repositoryFactory = new RepositoryFactory({ apiKey });
const qualityMetricsRepository = repositoryFactory.createQualityMetricsRepository();
const deps: QualityMetricsHandlerDeps = {
qualityMetricsRepository,
logger,
};
const handler = createQualityMetricsHandlerWithRepo(deps);
const result = await handler(params);
return result;
} catch (error) {
// Handle configuration errors and other setup issues
return MCPErrorFormatter.createErrorResponse(error, 'quality-metrics-setup');
}
}
/**
* Creates an update metric threshold handler with injected dependencies
* @param deps - The dependencies for the handler
* @returns The configured handler factory
*/
export const createUpdateMetricThresholdHandler: HandlerFactory<
BaseHandlerDeps,
DeepsourceUpdateMetricThresholdParams
> = createBaseHandlerFactory(
'update_metric_threshold',
async (deps, { projectKey, repositoryId, metricShortcode, metricKey, thresholdValue }) => {
const apiKey = deps.getApiKey();
deps.logger.debug('API key retrieved from config', {
length: apiKey.length,
prefix: `${apiKey.substring(0, 5)}...`,
});
const client = new DeepSourceClient(apiKey);
deps.logger.info('Updating metric threshold', {
projectKey,
repositoryId,
metricShortcode,
metricKey,
thresholdValue,
});
const result = await client.setMetricThreshold({
repositoryId,
metricShortcode,
metricKey,
thresholdValue: thresholdValue ?? null,
});
deps.logger.info('Metric threshold update result', {
success: result.ok,
projectKey,
metricShortcode,
metricKey,
});
const updateResult = {
ok: result.ok,
projectKey, // Echo back the project key for context
metricShortcode,
metricKey,
thresholdValue,
message: result.ok
? `Successfully ${thresholdValue !== null && thresholdValue !== undefined ? 'updated' : 'removed'} threshold for ${metricShortcode} (${metricKey})`
: `Failed to update threshold for ${metricShortcode} (${metricKey})`,
next_steps: result.ok
? ['Use quality_metrics to view the updated metrics']
: ['Check if you have sufficient permissions', 'Verify the repository ID is correct'],
};
return wrapInApiResponse(updateResult);
}
);
/**
* Updates the threshold for a specific metric in a project
* @param params - Parameters for updating the threshold
* @returns A response indicating whether the update was successful
* @throws Error if the DEEPSOURCE_API_KEY environment variable is not set
* @public
*/
export async function handleDeepsourceUpdateMetricThreshold(
params: DeepsourceUpdateMetricThresholdParams
) {
const deps = createDefaultHandlerDeps({ logger });
const handler = createUpdateMetricThresholdHandler(deps);
return handler(params);
}
/**
* Creates an update metric setting handler with injected dependencies
* @param deps - The dependencies for the handler
* @returns The configured handler factory
*/
export const createUpdateMetricSettingHandler: HandlerFactory<
BaseHandlerDeps,
DeepsourceUpdateMetricSettingParams
> = createBaseHandlerFactory(
'update_metric_setting',
async (deps, { projectKey, repositoryId, metricShortcode, isReported, isThresholdEnforced }) => {
const apiKey = deps.getApiKey();
deps.logger.debug('API key retrieved from config', {
length: apiKey.length,
prefix: `${apiKey.substring(0, 5)}...`,
});
const client = new DeepSourceClient(apiKey);
deps.logger.info('Updating metric setting', {
projectKey,
repositoryId,
metricShortcode,
isReported,
isThresholdEnforced,
});
const result = await client.updateMetricSetting({
repositoryId,
metricShortcode,
isReported,
isThresholdEnforced,
});
deps.logger.info('Metric setting update result', {
success: result.ok,
projectKey,
metricShortcode,
});
const updateResult = {
ok: result.ok,
projectKey, // Echo back the project key for context
metricShortcode,
settings: {
isReported,
isThresholdEnforced,
},
message: result.ok
? `Successfully updated settings for ${metricShortcode}`
: `Failed to update settings for ${metricShortcode}`,
next_steps: result.ok
? ['Use quality_metrics to view the updated metrics']
: ['Check if you have sufficient permissions', 'Verify the repository ID is correct'],
};
return wrapInApiResponse(updateResult);
}
);
/**
* Updates the settings for a specific metric in a project
* @param params - Parameters for updating the metric settings
* @returns A response indicating whether the update was successful
* @throws Error if the DEEPSOURCE_API_KEY environment variable is not set
* @public
*/
export async function handleDeepsourceUpdateMetricSetting(
params: DeepsourceUpdateMetricSettingParams
) {
const deps = createDefaultHandlerDeps({ logger });
const handler = createUpdateMetricSettingHandler(deps);
return handler(params);
}