deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
1,054 lines • 40.1 kB
JavaScript
#!/usr/bin/env node
/**
* @fileoverview DeepSource MCP Server for integrating DeepSource with Model Context Protocol.
* This module exports MCP server functions for DeepSource API integration.
* @packageDocumentation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { createLogger } from './utils/logging/logger.js';
import { handleProjects, handleDeepsourceQualityMetrics, handleDeepsourceUpdateMetricThreshold, handleDeepsourceUpdateMetricSetting, handleDeepsourceComplianceReport, handleDeepsourceProjectIssues, handleDeepsourceProjectRuns, handleDeepsourceRun, handleDeepsourceRecentRunIssues, handleDeepsourceDependencyVulnerabilities, } from './handlers/index.js';
import { MetricShortcode, ReportType } from './deepsource.js';
import { logToolInvocation, logToolResult, logAndFormatError } from './server/tool-helpers.js';
// Create logger instance for index.ts
const logger = createLogger('DeepSourceMCP:index');
// Initialize MCP server
/**
* The main MCP server instance
* @public
*/
export const mcpServer = new McpServer({
name: 'deepsource-mcp-server',
version: '1.2.0',
});
// Register the projects tool
mcpServer.registerTool('projects', {
description: 'List all available DeepSource projects. Returns a list of project objects with "key" and "name" properties.',
outputSchema: {
projects: z.array(z.object({
key: z.string(),
name: z.string(),
})),
},
},
// eslint-disable-next-line no-unused-vars
async (_extra) => {
try {
logToolInvocation('projects');
// Call the projects handler
const result = await handleProjects();
logToolResult('projects', result);
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Projects handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the project array
logger.debug('Parsing JSON result');
const projects = JSON.parse(result.content[0].text);
logger.info('Successfully processed projects', {
count: projects?.length || 0,
success: true,
});
return {
content: result.content,
structuredContent: { projects: projects || [] },
isError: false,
};
}
catch (error) {
const errorMessage = logAndFormatError(error, 'projects');
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: { projects: [] },
isError: true,
};
}
});
// Register the quality_metrics tool
mcpServer.registerTool('quality_metrics', {
description: 'Get quality metrics from a DeepSource project with optional filtering',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to fetch quality metrics for'),
shortcodeIn: z
.array(z.nativeEnum(MetricShortcode))
.optional()
.describe('Optional filter for specific metric shortcodes'),
},
outputSchema: {
metrics: z.array(z.object({
name: z.string(),
shortcode: z.string(),
description: z.string(),
positiveDirection: z.string(),
unit: z.string(),
minValueAllowed: z.number().nullable(),
maxValueAllowed: z.number().nullable(),
isReported: z.boolean(),
isThresholdEnforced: z.boolean(),
items: z.array(z.object({
id: z.string(),
key: z.string(),
threshold: z.number().nullable(),
latestValue: z.number().nullable(),
latestValueDisplay: z.string().nullable(),
thresholdStatus: z.string(),
thresholdInfo: z
.object({
difference: z.number(),
percentDifference: z.string(),
isPassing: z.boolean(),
})
.nullable(),
})),
})),
},
}, async (params) => {
try {
logger.info('MCP quality_metrics tool handler invoked', { projectKey: params.projectKey });
// Call the quality metrics handler
const result = await handleDeepsourceQualityMetrics(params);
logger.debug('handleDeepsourceQualityMetrics result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// Parse the JSON string to get the metrics data
const metricsData = JSON.parse(result.content[0].text);
logger.info('Successfully processed quality metrics', {
count: metricsData?.metrics?.length || 0,
success: true,
});
return {
content: result.content,
structuredContent: metricsData,
isError: false,
};
}
catch (error) {
logger.error('Error in quality_metrics tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve quality metrics';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: { metrics: [] },
isError: true,
};
}
});
// Register the update_metric_threshold tool
mcpServer.registerTool('update_metric_threshold', {
description: 'Update the threshold for a specific quality metric',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to identify the project'),
repositoryId: z.string().describe('Repository GraphQL ID'),
metricShortcode: z.nativeEnum(MetricShortcode).describe('Code for the metric to update'),
metricKey: z.string().describe('Context key for the metric'),
thresholdValue: z
.number()
.nullable()
.optional()
.describe('New threshold value, or null to remove'),
},
outputSchema: {
ok: z.boolean(),
projectKey: z.string(),
metricShortcode: z.string(),
metricKey: z.string(),
thresholdValue: z.number().nullable().optional(),
message: z.string(),
next_steps: z.array(z.string()),
},
}, async (params) => {
try {
logger.info('MCP update_metric_threshold tool handler invoked', {
projectKey: params.projectKey,
metricShortcode: params.metricShortcode,
metricKey: params.metricKey,
});
// Call the update metric threshold handler with metricKey type coercion
const result = await handleDeepsourceUpdateMetricThreshold({
...params,
metricKey: params.metricKey,
});
logger.debug('handleDeepsourceUpdateMetricThreshold result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// Parse the JSON string to get the result
const updateResult = JSON.parse(result.content[0].text);
logger.info('Metric threshold update result', {
success: updateResult.ok,
metric: `${updateResult.metricShortcode}:${updateResult.metricKey}`,
});
return {
content: result.content,
structuredContent: updateResult,
isError: !updateResult.ok,
};
}
catch (error) {
logger.error('Error in update_metric_threshold tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to update metric threshold';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
ok: false,
projectKey: params.projectKey,
metricShortcode: params.metricShortcode,
metricKey: params.metricKey,
message: errorMessage,
next_steps: ['Check your API key permissions', 'Verify input parameters'],
},
isError: true,
};
}
});
// Register the update_metric_setting tool
mcpServer.registerTool('update_metric_setting', {
description: 'Update the settings for a quality metric',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to identify the project'),
repositoryId: z.string().describe('Repository GraphQL ID'),
metricShortcode: z.nativeEnum(MetricShortcode).describe('Code for the metric to update'),
isReported: z.boolean().describe('Whether the metric should be reported'),
isThresholdEnforced: z.boolean().describe('Whether the threshold should be enforced'),
},
outputSchema: {
ok: z.boolean(),
projectKey: z.string(),
metricShortcode: z.string(),
settings: z.object({
isReported: z.boolean(),
isThresholdEnforced: z.boolean(),
}),
message: z.string(),
next_steps: z.array(z.string()),
},
}, async (params) => {
try {
logger.info('MCP update_metric_setting tool handler invoked', {
projectKey: params.projectKey,
metricShortcode: params.metricShortcode,
});
// Call the update metric setting handler
const result = await handleDeepsourceUpdateMetricSetting(params);
logger.debug('handleDeepsourceUpdateMetricSetting result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// Parse the JSON string to get the result
const updateResult = JSON.parse(result.content[0].text);
logger.info('Metric setting update result', {
success: updateResult.ok,
metric: updateResult.metricShortcode,
});
return {
content: result.content,
structuredContent: updateResult,
isError: !updateResult.ok,
};
}
catch (error) {
logger.error('Error in update_metric_setting tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to update metric setting';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
ok: false,
projectKey: params.projectKey,
metricShortcode: params.metricShortcode,
settings: {
isReported: params.isReported,
isThresholdEnforced: params.isThresholdEnforced,
},
message: errorMessage,
next_steps: ['Check your API key permissions', 'Verify input parameters'],
},
isError: true,
};
}
});
// Register the compliance_report tool
mcpServer.registerTool('compliance_report', {
description: 'Get security compliance reports from a DeepSource project',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to identify the project'),
reportType: z.nativeEnum(ReportType).describe('Type of compliance report to fetch'),
},
outputSchema: {
key: z.string(),
title: z.string(),
currentValue: z.number().nullable(),
status: z.string(),
securityIssueStats: z.array(z.object({
key: z.string(),
title: z.string(),
occurrence: z.object({
critical: z.number(),
major: z.number(),
minor: z.number(),
total: z.number(),
}),
})),
trends: z.record(z.string(), z.any()).optional(),
analysis: z.object({
summary: z.string(),
status_explanation: z.string(),
critical_issues: z.number(),
major_issues: z.number(),
minor_issues: z.number(),
total_issues: z.number(),
}),
recommendations: z.object({
actions: z.array(z.string()),
resources: z.array(z.string()),
}),
},
}, async (params) => {
try {
logger.info('MCP compliance_report tool handler invoked', {
projectKey: params.projectKey,
reportType: params.reportType,
});
// Call the compliance report handler
const result = await handleDeepsourceComplianceReport(params);
logger.debug('handleDeepsourceComplianceReport result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// Parse the JSON string to get the report data
const reportData = JSON.parse(result.content[0].text);
logger.info('Successfully processed compliance report', {
reportType: params.reportType,
reportTitle: reportData.title,
status: reportData.status,
});
return {
content: result.content,
structuredContent: reportData,
isError: false,
};
}
catch (error) {
logger.error('Error in compliance_report tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve compliance report';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
key: '',
title: '',
currentValue: null,
status: 'ERROR',
securityIssueStats: [],
analysis: {
summary: 'Error retrieving report',
status_explanation: errorMessage,
critical_issues: 0,
major_issues: 0,
minor_issues: 0,
total_issues: 0,
},
recommendations: {
actions: ['Check your API key permissions', 'Verify input parameters'],
resources: [],
},
},
isError: true,
};
}
});
// Register the project_issues tool
mcpServer.registerTool('project_issues', {
description: 'Get issues from a DeepSource project with filtering capabilities',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to fetch issues for'),
path: z.string().optional().describe('Filter issues by file path'),
analyzerIn: z.array(z.string()).optional().describe('Filter issues by analyzer shortcodes'),
tags: z.array(z.string()).optional().describe('Filter issues by tags'),
first: z.number().optional().describe('Number of items to retrieve (forward pagination)'),
after: z
.string()
.optional()
.describe('Cursor to start retrieving items after (forward pagination)'),
last: z.number().optional().describe('Number of items to retrieve (backward pagination)'),
before: z
.string()
.optional()
.describe('Cursor to start retrieving items before (backward pagination)'),
},
outputSchema: {
issues: z.array(z.object({
id: z.string(),
title: z.string(),
shortcode: z.string(),
category: z.string(),
severity: z.string(),
status: z.string(),
issue_text: z.string(),
file_path: z.string(),
line_number: z.number(),
tags: z.array(z.string()),
})),
pageInfo: z.object({
hasNextPage: z.boolean(),
hasPreviousPage: z.boolean(),
startCursor: z.string().nullable(),
endCursor: z.string().nullable(),
}),
totalCount: z.number(),
},
}, async (params) => {
try {
logger.info('MCP project_issues tool handler invoked', {
projectKey: params.projectKey,
hasPathFilter: Boolean(params.path),
hasAnalyzerInFilter: Boolean(params.analyzerIn),
hasTagsFilter: Boolean(params.tags),
});
// Call the project issues handler
const result = await handleDeepsourceProjectIssues(params);
logger.debug('handleDeepsourceProjectIssues result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Project issues handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the issues data
const issuesData = JSON.parse(result.content[0].text);
logger.info('Successfully processed project issues', {
count: issuesData?.issues?.length || 0,
totalCount: issuesData?.totalCount || 0,
hasNextPage: issuesData?.pageInfo?.hasNextPage || false,
success: true,
});
return {
content: result.content,
structuredContent: {
issues: issuesData.issues,
pageInfo: issuesData.pageInfo,
totalCount: issuesData.totalCount,
},
isError: false,
};
}
catch (error) {
logger.error('Error in project_issues tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve project issues';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
issues: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
totalCount: 0,
},
isError: true,
};
}
});
// Register the runs tool
mcpServer.registerTool('runs', {
description: 'List analysis runs for a DeepSource project with filtering',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to fetch runs for'),
analyzerIn: z.array(z.string()).optional().describe('Filter runs by analyzer shortcodes'),
first: z.number().optional().describe('Number of items to retrieve (forward pagination)'),
after: z
.string()
.optional()
.describe('Cursor to start retrieving items after (forward pagination)'),
last: z.number().optional().describe('Number of items to retrieve (backward pagination)'),
before: z
.string()
.optional()
.describe('Cursor to start retrieving items before (backward pagination)'),
},
outputSchema: {
runs: z.array(z.object({
id: z.string(),
runUid: z.string(),
commitOid: z.string(),
branchName: z.string(),
baseOid: z.string(),
status: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
finishedAt: z.string().optional(),
summary: z.object({
occurrencesIntroduced: z.number(),
occurrencesResolved: z.number(),
occurrencesSuppressed: z.number(),
occurrenceDistributionByAnalyzer: z
.array(z.object({
analyzerShortcode: z.string(),
introduced: z.number(),
}))
.optional(),
occurrenceDistributionByCategory: z
.array(z.object({
category: z.string(),
introduced: z.number(),
}))
.optional(),
}),
repository: z.object({
name: z.string(),
id: z.string(),
}),
})),
pageInfo: z.object({
hasNextPage: z.boolean(),
hasPreviousPage: z.boolean(),
startCursor: z.string().nullable(),
endCursor: z.string().nullable(),
}),
totalCount: z.number(),
},
}, async (params) => {
try {
logger.info('MCP runs tool handler invoked', {
projectKey: params.projectKey,
hasAnalyzerInFilter: Boolean(params.analyzerIn),
});
// Call the project runs handler with proper type assertion for analyzerIn
const result = await handleDeepsourceProjectRuns({
...params,
analyzerIn: params.analyzerIn,
});
logger.debug('handleDeepsourceProjectRuns result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Project runs handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the runs data
const runsData = JSON.parse(result.content[0].text);
logger.info('Successfully processed project runs', {
count: runsData?.runs?.length || 0,
totalCount: runsData?.totalCount || 0,
hasNextPage: runsData?.pageInfo?.hasNextPage || false,
success: true,
});
return {
content: result.content,
structuredContent: {
runs: runsData.runs,
pageInfo: runsData.pageInfo,
totalCount: runsData.totalCount,
},
isError: false,
};
}
catch (error) {
logger.error('Error in runs tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve project runs';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
runs: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
totalCount: 0,
},
isError: true,
};
}
});
// Register the run tool
mcpServer.registerTool('run', {
description: 'Get a specific analysis run by its runUid or commitOid',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to identify the project'),
runIdentifier: z.string().describe('The run identifier (runUid or commitOid)'),
isCommitOid: z
.boolean()
.optional()
.describe('Flag to indicate whether the runIdentifier is a commitOid (default: false)'),
},
outputSchema: {
run: z.object({
id: z.string(),
runUid: z.string(),
commitOid: z.string(),
branchName: z.string(),
baseOid: z.string(),
status: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
finishedAt: z.string().optional(),
summary: z.object({
occurrencesIntroduced: z.number(),
occurrencesResolved: z.number(),
occurrencesSuppressed: z.number(),
occurrenceDistributionByAnalyzer: z
.array(z.object({
analyzerShortcode: z.string(),
introduced: z.number(),
}))
.optional(),
occurrenceDistributionByCategory: z
.array(z.object({
category: z.string(),
introduced: z.number(),
}))
.optional(),
}),
repository: z.object({
name: z.string(),
id: z.string(),
}),
}),
analysis: z.object({
status_info: z.string(),
issue_summary: z.string(),
analyzers_used: z.array(z.string()),
issue_categories: z.array(z.string()),
}),
},
}, async (params) => {
try {
logger.info('MCP run tool handler invoked', {
projectKey: params.projectKey,
runIdentifier: params.runIdentifier,
isCommitOid: params.isCommitOid,
});
// Call the run handler
const result = await handleDeepsourceRun(params);
logger.debug('handleDeepsourceRun result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Run handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the run data
const runData = JSON.parse(result.content[0].text);
logger.info('Successfully processed run', {
runUid: runData?.run?.runUid,
commitOid: runData?.run?.commitOid,
branchName: runData?.run?.branchName,
status: runData?.run?.status,
success: true,
});
return {
content: result.content,
structuredContent: {
run: runData.run,
analysis: runData.analysis,
},
isError: false,
};
}
catch (error) {
logger.error('Error in run tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve run';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
run: {
id: '',
runUid: '',
commitOid: '',
branchName: '',
baseOid: '',
status: 'ERROR',
createdAt: '',
updatedAt: '',
summary: {
occurrencesIntroduced: 0,
occurrencesResolved: 0,
occurrencesSuppressed: 0,
},
repository: {
name: '',
id: '',
},
},
analysis: {
status_info: errorMessage,
issue_summary: 'No data available',
analyzers_used: [],
issue_categories: [],
},
},
isError: true,
};
}
});
// Register the recent_run_issues tool
mcpServer.registerTool('recent_run_issues', {
description: 'Get issues from the most recent analysis run on a specific branch',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to fetch issues for'),
branchName: z.string().describe('Branch name to fetch the most recent run from'),
first: z.number().optional().describe('Number of items to retrieve (forward pagination)'),
after: z
.string()
.optional()
.describe('Cursor to start retrieving items after (forward pagination)'),
last: z.number().optional().describe('Number of items to retrieve (backward pagination)'),
before: z
.string()
.optional()
.describe('Cursor to start retrieving items before (backward pagination)'),
},
outputSchema: {
run: z.object({
id: z.string(),
runUid: z.string(),
commitOid: z.string(),
branchName: z.string(),
baseOid: z.string(),
status: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
finishedAt: z.string().optional(),
summary: z.object({
occurrencesIntroduced: z.number(),
occurrencesResolved: z.number(),
occurrencesSuppressed: z.number(),
occurrenceDistributionByAnalyzer: z
.array(z.object({
analyzerShortcode: z.string(),
introduced: z.number(),
}))
.optional(),
occurrenceDistributionByCategory: z
.array(z.object({
category: z.string(),
introduced: z.number(),
}))
.optional(),
}),
repository: z.object({
name: z.string(),
id: z.string(),
}),
}),
issues: z.array(z.object({
id: z.string(),
title: z.string(),
shortcode: z.string(),
category: z.string(),
severity: z.string(),
status: z.string(),
issue_text: z.string(),
file_path: z.string(),
line_number: z.number(),
tags: z.array(z.string()),
})),
pageInfo: z.object({
hasNextPage: z.boolean(),
hasPreviousPage: z.boolean(),
startCursor: z.string().nullable(),
endCursor: z.string().nullable(),
}),
totalCount: z.number(),
},
}, async (params) => {
try {
logger.info('MCP recent_run_issues tool handler invoked', {
projectKey: params.projectKey,
branchName: params.branchName,
});
// Call the recent run issues handler
const result = await handleDeepsourceRecentRunIssues(params);
logger.debug('handleDeepsourceRecentRunIssues result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Recent run issues handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the issues and run data
const data = JSON.parse(result.content[0].text);
logger.info('Successfully processed recent run issues', {
runUid: data?.run?.runUid,
branchName: data?.run?.branchName,
issueCount: data?.issues?.length || 0,
totalCount: data?.totalCount || 0,
hasNextPage: data?.pageInfo?.hasNextPage || false,
success: true,
});
return {
content: result.content,
structuredContent: {
run: data.run,
issues: data.issues,
pageInfo: data.pageInfo,
totalCount: data.totalCount,
},
isError: false,
};
}
catch (error) {
logger.error('Error in recent_run_issues tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve recent run issues';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
run: {
id: '',
runUid: '',
commitOid: '',
branchName: params.branchName || '',
baseOid: '',
status: 'ERROR',
createdAt: '',
updatedAt: '',
summary: {
occurrencesIntroduced: 0,
occurrencesResolved: 0,
occurrencesSuppressed: 0,
},
repository: {
name: '',
id: '',
},
},
issues: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
totalCount: 0,
},
isError: true,
};
}
});
// Register the dependency_vulnerabilities tool
mcpServer.registerTool('dependency_vulnerabilities', {
description: 'Get dependency vulnerabilities from a DeepSource project',
inputSchema: {
projectKey: z.string().describe('DeepSource project key to fetch vulnerabilities for'),
first: z.number().optional().describe('Number of items to retrieve (forward pagination)'),
after: z
.string()
.optional()
.describe('Cursor to start retrieving items after (forward pagination)'),
last: z.number().optional().describe('Number of items to retrieve (backward pagination)'),
before: z
.string()
.optional()
.describe('Cursor to start retrieving items before (backward pagination)'),
},
outputSchema: {
vulnerabilities: z.array(z.object({
id: z.string(),
title: z.string(),
severity: z.string(),
cvssScore: z.number().nullable(),
packageName: z.string(),
packageVersion: z.string(),
fixedIn: z.string().nullable(),
description: z.string(),
identifiers: z.record(z.string(), z.array(z.string())),
references: z.array(z.string()),
risk_assessment: z.object({
severity_level: z.string(),
cvss_description: z.string(),
fixed_version_available: z.boolean(),
remediation_advice: z.string(),
}),
})),
pageInfo: z.object({
hasNextPage: z.boolean(),
hasPreviousPage: z.boolean(),
startCursor: z.string().nullable(),
endCursor: z.string().nullable(),
}),
totalCount: z.number(),
},
}, async (params) => {
try {
logger.info('MCP dependency_vulnerabilities tool handler invoked', {
projectKey: params.projectKey,
});
// Call the dependency vulnerabilities handler
const result = await handleDeepsourceDependencyVulnerabilities(params);
logger.debug('handleDeepsourceDependencyVulnerabilities result received', {
contentLength: result.content?.[0]?.text?.length || 0,
contentPreview: `${result.content?.[0]?.text?.substring(0, 50)}...`,
});
// If the handler returned an error, throw it to be caught below
if (result.isError) {
const errorObject = JSON.parse(result.content[0].text);
logger.error('Dependency vulnerabilities handler returned error', errorObject);
throw new Error(result.content[0].text);
}
// Parse the JSON string to get the vulnerabilities data
const vulnerabilitiesData = JSON.parse(result.content[0].text);
logger.info('Successfully processed dependency vulnerabilities', {
count: vulnerabilitiesData?.vulnerabilities?.length || 0,
totalCount: vulnerabilitiesData?.totalCount || 0,
hasNextPage: vulnerabilitiesData?.pageInfo?.hasNextPage || false,
success: true,
});
return {
content: result.content,
structuredContent: {
vulnerabilities: vulnerabilitiesData.vulnerabilities,
pageInfo: vulnerabilitiesData.pageInfo,
totalCount: vulnerabilitiesData.totalCount,
},
isError: false,
};
}
catch (error) {
logger.error('Error in dependency_vulnerabilities tool handler', {
errorType: typeof error,
errorMessage: error instanceof Error ? error.message : String(error),
});
let errorMessage = 'Failed to retrieve dependency vulnerabilities';
if (error instanceof Error) {
errorMessage = error.message;
}
return {
content: [
{
type: 'text',
text: errorMessage,
},
],
structuredContent: {
vulnerabilities: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
totalCount: 0,
},
isError: true,
};
}
});
// Only start the server if not in test mode
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'test') {
const transport = new StdioServerTransport();
logger.info('Starting MCP server...');
await mcpServer.connect(transport);
logger.info('MCP server started successfully');
}
//# sourceMappingURL=index.js.map