Skip to main content
Glama
run.ts11.7 kB
/** * @fileoverview Run handler for the DeepSource MCP server * This module provides MCP tool handlers for fetching a specific DeepSource analysis run. */ import { DeepSourceClient, OccurrenceDistributionByAnalyzer, OccurrenceDistributionByCategory, } from '../deepsource.js'; import { ApiResponse } from '../models/common.js'; import { createLogger, Logger } from '../utils/logging/logger.js'; import { BaseHandlerDeps } from './base/handler.interface.js'; import { createBaseHandlerFactory, wrapInApiResponse, createDefaultHandlerDeps, } from './base/handler.factory.js'; import { IAnalysisRunRepository } from '../domain/aggregates/analysis-run/analysis-run.repository.js'; import { AnalysisRun } from '../domain/aggregates/analysis-run/analysis-run.aggregate.js'; import { RepositoryFactory } from '../infrastructure/factories/repository.factory.js'; import { asProjectKey, asRunId, asCommitOid } from '../types/branded.js'; // Logger for the run handler const logger = createLogger('RunHandler'); /** * Interface for parameters for fetching a specific run * @public */ export interface DeepsourceRunParams { /** DeepSource project key to fetch the run from */ projectKey: string; /** The run identifier (runUid or commitOid) */ runIdentifier: string; /** Flag to indicate whether the runIdentifier is a commitOid (default: false) */ isCommitOid?: boolean; } /** * Extended dependencies interface for run handler */ interface RunHandlerDeps { analysisRunRepository: IAnalysisRunRepository; logger: Logger; } /** * Creates a run handler with injected dependencies using domain aggregates * @param deps - The dependencies for the handler * @returns The configured handler function */ export function createRunHandlerWithRepo(deps: RunHandlerDeps) { return async function handleRun(params: DeepsourceRunParams) { try { const { projectKey, runIdentifier, isCommitOid = false } = params; const projectKeyBranded = asProjectKey(projectKey); deps.logger.info('Fetching run from repository', { projectKey, runIdentifier, identifierType: isCommitOid ? 'commitOid' : 'runUid', }); let domainRun: AnalysisRun | null; if (isCommitOid) { // Search by commit OID const commitOidBranded = asCommitOid(runIdentifier); domainRun = await deps.analysisRunRepository.findByCommit( projectKeyBranded, commitOidBranded ); } else { // Search by run ID (assuming runIdentifier is the runId) const runIdBranded = asRunId(runIdentifier); domainRun = await deps.analysisRunRepository.findByRunId(runIdBranded); } if (!domainRun) { deps.logger.error('Run not found', { projectKey, runIdentifier, identifierType: isCommitOid ? 'commitOid' : 'runUid', }); throw new Error( `Run with ${isCommitOid ? 'commitOid' : 'runUid'} "${runIdentifier}" not found` ); } deps.logger.info('Successfully fetched run', { runId: domainRun.runId, commitOid: domainRun.commitInfo.oid, branchName: domainRun.commitInfo.branch, status: domainRun.status, }); const runData = { run: { id: domainRun.runId, runUid: domainRun.runId, // Domain aggregate uses runId as the unique identifier commitOid: domainRun.commitInfo.oid, branchName: domainRun.commitInfo.branch, baseOid: domainRun.commitInfo.baseOid, status: domainRun.status, createdAt: domainRun.timestamps.createdAt, updatedAt: domainRun.timestamps.startedAt || domainRun.timestamps.createdAt, finishedAt: domainRun.timestamps.finishedAt, summary: { occurrencesIntroduced: domainRun.summary.totalIntroduced.count, occurrencesResolved: domainRun.summary.totalResolved.count, occurrencesSuppressed: domainRun.summary.totalSuppressed.count, occurrenceDistributionByAnalyzer: domainRun.summary.byAnalyzer.map((dist) => ({ analyzerShortcode: dist.analyzerShortcode, introduced: dist.introduced.count, })), occurrenceDistributionByCategory: domainRun.summary.byCategory.map((dist) => ({ category: dist.category, introduced: dist.introduced.count, })), }, repository: { name: 'Repository', // Domain aggregate doesn't store repository name directly id: domainRun.repositoryId, }, }, // Provide helpful guidance and related information analysis: { status_info: getStatusInfo(domainRun.status), issue_summary: `This run introduced ${domainRun.summary.totalIntroduced.count} issues, resolved ${domainRun.summary.totalResolved.count} issues, and suppressed ${domainRun.summary.totalSuppressed.count} issues.`, analyzers_used: domainRun.summary.byAnalyzer?.map((a) => a.analyzerShortcode) || [], issue_categories: domainRun.summary.byCategory?.map((c) => c.category) || [], }, related_tools: { issues: 'Use the project_issues tool to get all issues in the project', runs: 'Use the runs tool to list all runs for the project', recent_issues: 'Use the recent_run_issues tool to get issues from the most recent run on a branch', }, }; return { content: [ { type: 'text' as const, text: JSON.stringify(runData), }, ], }; } catch (error) { deps.logger.error('Error in handleRun', { 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', }); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; deps.logger.debug('Returning error response', { errorMessage }); return { isError: true, content: [ { type: 'text' as const, text: JSON.stringify({ error: errorMessage, details: 'Failed to retrieve run', }), }, ], }; } }; } /** * Creates a run handler with injected dependencies * @param deps - The dependencies for the handler * @returns The configured handler factory */ export const createRunHandler = createBaseHandlerFactory( 'run', async ( deps: BaseHandlerDeps, { projectKey, runIdentifier, isCommitOid = false }: DeepsourceRunParams ) => { 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 run', { projectKey, runIdentifier, identifierType: isCommitOid ? 'commitOid' : 'runUid', }); const run = await client.getRun(runIdentifier); if (!run) { deps.logger.error('Run not found', { projectKey, runIdentifier, identifierType: isCommitOid ? 'commitOid' : 'runUid', }); throw new Error( `Run with ${isCommitOid ? 'commitOid' : 'runUid'} "${runIdentifier}" not found in project "${projectKey}"` ); } deps.logger.info('Successfully fetched run', { runUid: run.runUid, commitOid: run.commitOid, branchName: run.branchName, status: run.status, }); const runData = { run: { id: run.id, runUid: run.runUid, commitOid: run.commitOid, branchName: run.branchName, baseOid: run.baseOid, status: run.status, createdAt: run.createdAt, updatedAt: run.updatedAt, finishedAt: run.finishedAt, summary: { occurrencesIntroduced: run.summary.occurrencesIntroduced, occurrencesResolved: run.summary.occurrencesResolved, occurrencesSuppressed: run.summary.occurrencesSuppressed, occurrenceDistributionByAnalyzer: run.summary.occurrenceDistributionByAnalyzer, occurrenceDistributionByCategory: run.summary.occurrenceDistributionByCategory, }, repository: { name: run.repository.name, id: run.repository.id, }, }, // Provide helpful guidance and related information analysis: { status_info: getStatusInfo(run.status), issue_summary: `This run introduced ${run.summary.occurrencesIntroduced} issues, resolved ${run.summary.occurrencesResolved} issues, and suppressed ${run.summary.occurrencesSuppressed} issues.`, analyzers_used: run.summary.occurrenceDistributionByAnalyzer?.map( (a: OccurrenceDistributionByAnalyzer) => a.analyzerShortcode ) || [], issue_categories: run.summary.occurrenceDistributionByCategory?.map( (c: OccurrenceDistributionByCategory) => c.category ) || [], }, related_tools: { issues: 'Use the project_issues tool to get all issues in the project', runs: 'Use the runs tool to list all runs for the project', recent_issues: 'Use the recent_run_issues tool to get issues from the most recent run on a branch', }, }; return wrapInApiResponse(runData); } ); /** * Fetches and returns information about a specific analysis run using domain aggregates * @param params - Parameters for fetching the run, including project key and run identifier * @returns A response containing the run data * @throws Error if the DEEPSOURCE_API_KEY environment variable is not set * @public */ export async function handleDeepsourceRun(params: DeepsourceRunParams): Promise<ApiResponse> { const baseDeps = createDefaultHandlerDeps({ logger }); const apiKey = baseDeps.getApiKey(); const repositoryFactory = new RepositoryFactory({ apiKey }); const analysisRunRepository = repositoryFactory.createAnalysisRunRepository(); const deps: RunHandlerDeps = { analysisRunRepository, logger, }; const handler = createRunHandlerWithRepo(deps); const result = await handler(params); // If the domain handler returned an error response, throw an error for backward compatibility if (result.isError) { const firstContent = result.content[0]; if (firstContent) { const errorData = JSON.parse(firstContent.text); throw new Error(errorData.error); } else { throw new Error('Unknown run error'); } } return result; } /** * Helper function to get human-readable status information * @param status The run status * @returns Information about the status * @private */ function getStatusInfo(status: string): string { switch (status) { case 'PENDING': return 'The run is currently queued and waiting to be processed.'; case 'SUCCESS': return 'The run completed successfully with all analyzers.'; case 'FAILURE': return 'The run failed due to an error during analysis.'; case 'TIMEOUT': return 'The run exceeded the maximum allowed time and was terminated.'; case 'CANCEL': return 'The run was manually cancelled.'; case 'READY': return 'The run is ready to be processed but not yet started.'; case 'SKIPPED': return 'The run was skipped, possibly due to no code changes detected.'; default: return `Unknown status: ${status}`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server