import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { IssuesApi, ProjectsApi, Configuration } from './generated';
import { allTools } from './tools';
import { SnykProxyClient } from './snyk-proxy';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
/**
* Create MCP Server with all registered tools
*/
export function createMCPServer(apiKey?: string) {
// Snyk API Configuration
const config = new Configuration({
apiKey: apiKey || process.env.SNYK_API_KEY,
basePath: 'https://api.snyk.io/rest',
});
const issuesApi = new IssuesApi(config);
const projectsApi = new ProjectsApi(config);
// Create MCP Server using high-level API
const server = new McpServer(
{
name: 'snyk-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Register all tools
allTools.forEach((tool) => {
server.registerTool(
tool.name,
{
description: tool.description,
inputSchema: tool.inputSchema,
},
async (args: any) => {
try {
return await tool.handler(args, {
issuesApi,
projectsApi,
});
} catch (error) {
if (error instanceof Error) {
throw error;
}
throw new Error('Unknown error occurred');
}
}
);
});
return server;
}
/**
* Start MCP Server on stdio transport
* This function is called when running as a standalone server
*/
export async function startMCPServer() {
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
const { config: loadEnv } = await import('dotenv');
// Load .env file when running locally
loadEnv({ debug: false });
const server = createMCPServer();
// Check if Snyk CLI proxy is enabled (default: true)
const proxyEnabled = process.env.SNYK_CLI_PROXY_ENABLED !== 'false'
&& process.env.SNYK_CLI_PROXY_ENABLED !== '0'
&& process.env.SNYK_CLI_PROXY_ENABLED !== 'no';
if (proxyEnabled) {
// Initialize and connect to Snyk CLI Proxy
try {
const snykProxy = new SnykProxyClient();
await snykProxy.connect();
const snykTools = await snykProxy.listTools();
const snykToolNames = new Set(snykTools.map((t) => t.name));
// Hack: Access internal server to wrap handlers
const internalServer = (server as any).server;
const originalListHandler = internalServer._requestHandlers.get('tools/list');
const originalCallHandler = internalServer._requestHandlers.get('tools/call');
internalServer.setRequestHandler(ListToolsRequestSchema, async (req: any) => {
const result = await originalListHandler(req);
return {
tools: [...result.tools, ...snykTools],
nextCursor: result.nextCursor,
};
});
internalServer.setRequestHandler(CallToolRequestSchema, async (req: any) => {
if (snykToolNames.has(req.params.name)) {
return await snykProxy.callTool(req.params.name, req.params.arguments);
}
return await originalCallHandler(req);
});
console.error(`Registered ${snykTools.length} Snyk CLI tools`);
} catch (error) {
console.error('Failed to connect to Snyk CLI proxy:', error);
}
} else {
console.error('Snyk CLI proxy disabled via SNYK_CLI_PROXY_ENABLED environment variable');
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Snyk MCP Server running on stdio');
}
// If this file is executed directly (not imported), start the server
if (require.main === module) {
startMCPServer().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});
}