import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import crypto from "crypto";
const API_ID = process.env.VERACODE_API_ID!;
const API_SECRET = process.env.VERACODE_API_SECRET!;
const BASE_URL = "https://api.veracode.com";
// --- HMAC-SHA-256 Auth ---
function hmac256(data: string | Buffer, key: Buffer, format?: "hex") {
const h = crypto.createHmac("sha256", key).update(data);
return format ? Buffer.from(h.digest(format), "utf-8") : h.digest();
}
function hexToBytes(hex: string): Buffer {
return Buffer.from(hex, "hex");
}
function generateAuthHeader(urlPath: string, method: string): string {
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString("hex");
const hashedNonce = hmac256(hexToBytes(nonce), hexToBytes(API_SECRET));
const hashedTimestamp = hmac256(timestamp, hashedNonce);
const signingKey = hmac256("vcode_request_version_1", hashedTimestamp);
const data = `id=${API_ID}&host=api.veracode.com&url=${urlPath}&method=${method.toUpperCase()}`;
const signature = crypto
.createHmac("sha256", signingKey)
.update(data)
.digest("hex");
return `VERACODE-HMAC-SHA-256 id=${API_ID},ts=${timestamp},nonce=${nonce},sig=${signature}`;
}
// --- API fetch ---
async function veracodeFetch(path: string, params?: Record<string, string>) {
const url = new URL(path, BASE_URL);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== "") url.searchParams.set(k, v);
}
}
const urlPath = url.pathname + url.search;
const auth = generateAuthHeader(urlPath, "GET");
const res = await fetch(url.toString(), {
headers: { Authorization: auth },
});
if (!res.ok) {
const body = await res.text();
throw new Error(`Veracode ${res.status}: ${body}`);
}
return res.json();
}
// --- MCP Server ---
const server = new McpServer({
name: "veracode-mcp",
version: "1.0.0",
});
// Tool 1: veracode_applications
server.tool(
"veracode_applications",
"List and search Veracode application profiles",
{
name: z
.string()
.optional()
.describe("Filter by application name (partial match)"),
policyCompliance: z
.enum(["PASSED", "DID_NOT_PASS", "NOT_ASSESSED"])
.optional()
.describe("Filter by policy compliance status"),
scanType: z
.enum(["STATIC", "DYNAMIC", "MANUAL"])
.optional()
.describe("Filter by scan type"),
page: z.number().optional().describe("Page number (0-indexed, default 0)"),
size: z.number().optional().describe("Page size (default 50)"),
},
async ({ name, policyCompliance, scanType, page, size }) => {
const params: Record<string, string> = {};
if (name) params.name = name;
if (policyCompliance) params.policy_compliance = policyCompliance;
if (scanType) params.scan_type = scanType;
if (page !== undefined) params.page = String(page);
if (size !== undefined) params.size = String(size);
const data = await veracodeFetch("/appsec/v1/applications", params);
return {
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
};
}
);
// Tool 2: veracode_findings
server.tool(
"veracode_findings",
"Get vulnerability findings/flaws for a Veracode application",
{
applicationGuid: z.string().describe("Application GUID"),
scanType: z
.enum(["STATIC", "DYNAMIC", "MANUAL", "SCA"])
.optional()
.describe("Filter by scan type (SCA must be queried alone)"),
severity: z
.number()
.optional()
.describe("Exact severity level (0-5)"),
severityGte: z
.number()
.optional()
.describe("Minimum severity level (0-5)"),
violatesPolicy: z
.boolean()
.optional()
.describe("Only findings that violate policy"),
cwe: z.number().optional().describe("Filter by CWE ID"),
newFindings: z
.boolean()
.optional()
.describe("Only newly discovered findings"),
page: z.number().optional().describe("Page number (0-indexed)"),
size: z.number().optional().describe("Page size"),
},
async ({
applicationGuid,
scanType,
severity,
severityGte,
violatesPolicy,
cwe,
newFindings,
page,
size,
}) => {
const params: Record<string, string> = {};
if (scanType) params.scan_type = scanType;
if (severity !== undefined) params.severity = String(severity);
if (severityGte !== undefined) params.severity_gte = String(severityGte);
if (violatesPolicy) params.violates_policy = "TRUE";
if (cwe !== undefined) params.cwe = String(cwe);
if (newFindings) params.new = "true";
if (page !== undefined) params.page = String(page);
if (size !== undefined) params.size = String(size);
const data = await veracodeFetch(
`/appsec/v2/applications/${applicationGuid}/findings`,
params
);
return {
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
};
}
);
// Tool 3: veracode_scan_status
server.tool(
"veracode_scan_status",
"Get scan status and summary report for a Veracode application",
{
applicationGuid: z.string().describe("Application GUID"),
},
async ({ applicationGuid }) => {
const [app, summary] = await Promise.all([
veracodeFetch(`/appsec/v1/applications/${applicationGuid}`),
veracodeFetch(
`/appsec/v2/applications/${applicationGuid}/summary_report`
).catch(() => null),
]);
const result = { application: app, summary_report: summary };
return {
content: [
{ type: "text" as const, text: JSON.stringify(result, null, 2) },
],
};
}
);
// Tool 4: veracode_policy_compliance
server.tool(
"veracode_policy_compliance",
"Check policy compliance status for Veracode applications",
{
applicationGuid: z
.string()
.optional()
.describe("Specific application GUID (omit to list all non-compliant)"),
policyCompliance: z
.enum(["PASSED", "DID_NOT_PASS", "NOT_ASSESSED"])
.optional()
.describe("Filter by compliance status (default: DID_NOT_PASS)"),
page: z.number().optional().describe("Page number (0-indexed)"),
size: z.number().optional().describe("Page size"),
},
async ({ applicationGuid, policyCompliance, page, size }) => {
if (applicationGuid) {
const data = await veracodeFetch(
`/appsec/v1/applications/${applicationGuid}`
);
return {
content: [
{ type: "text" as const, text: JSON.stringify(data, null, 2) },
],
};
}
const params: Record<string, string> = {
policy_compliance: policyCompliance ?? "DID_NOT_PASS",
};
if (page !== undefined) params.page = String(page);
if (size !== undefined) params.size = String(size);
const data = await veracodeFetch("/appsec/v1/applications", params);
return {
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
};
}
);
// --- Start ---
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});