# Directory Structure
```
├── .github
│ └── workflows
│ └── build.yml
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
dist/
.vscode/
.venv/
venv/
__pycache__/
*.pyc
*.tmp
*.log
*.swp
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Snyk MCP Server
A standalone Model Context Protocol server for Snyk security scanning functionality.
**WARNING: THIS MCP SERVER IS CURRENTLY IN ALPHA AND IS NOT YET FINISHED!**
## Configuration
Update your Claude desktop config (`claude-config.json`):
```json
{
"mcpServers": {
"snyk": {
"command": "npx",
"args": [
"-y",
"github:sammcj/mcp-snyk"
],
"env": {
"SNYK_API_KEY": "your_snyk_token",
"SNYK_ORG_ID": "your_default_org_id" // Optional: Configure a default organisation ID
}
}
}
}
```
Replace the token with your actual Snyk API token. The organisation ID can be configured in multiple ways:
1. In the MCP settings via `SNYK_ORG_ID` (as shown above)
2. Using the Snyk CLI: `snyk config set org=your-org-id`
3. Providing it directly in commands
The server will try these methods in order until it finds a valid organisation ID.
### Verifying Configuration
You can verify your Snyk token is configured correctly by asking Claude to run the verify_token command:
```
Verify my Snyk token configuration
```
This will check if your token is valid and show your Snyk user information. If you have the Snyk CLI installed and configured, it will also show your CLI-configured organization ID.
## Features
- Repository security scanning using GitHub/GitLab URLs
- Snyk project scanning
- Integration with Claude desktop
- Token verification
- Multiple organization ID configuration options
- Snyk CLI integration for organization ID lookup
## Usage
To scan a repository, you must provide its GitHub or GitLab URL:
```
Scan repository https://github.com/owner/repo for security vulnerabilities
```
IMPORTANT: The scan_repository command requires the actual repository URL (e.g., https://github.com/owner/repo). Do not use local file paths - always use the repository's URL on GitHub or GitLab.
For Snyk projects:
```
Scan Snyk project project-id-here
```
### Organization ID Configuration
The server will look for the organization ID in this order:
1. Command argument (if provided)
2. MCP settings environment variable (`SNYK_ORG_ID`)
3. Snyk CLI configuration (`snyk config get org`)
You only need to specify the organization ID in your command if you want to override the configured values:
```
Scan repository https://github.com/owner/repo in organisation org-id-here
```
### Snyk CLI Integration
If you have the Snyk CLI installed (`npm install -g snyk`), the server can use it to:
- Get your default organisation ID
- Fall back to CLI configuration when MCP settings are not provided
- Show CLI configuration details in token verification output
This integration makes it easier to use the same organisation ID across both CLI and MCP server usage.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "Bundler",
"strict": true,
"outDir": "dist",
"skipLibCheck": true
},
"include": ["src"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@sammcj/mcp-server-snyk",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"bin": "dist/index.js",
"scripts": {
"build": "tsc",
"dev": "tsx watch src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.5.0",
"node-fetch": "^3.3.2",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.1"
},
"devDependencies": {
"@types/node": "^22.13.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
```yaml
name: Build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Commit dist
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add dist/
git commit -m "Add built files" || echo "No changes to commit"
git push
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { execSync } from 'child_process';
const SNYK_API_KEY = process.env.SNYK_API_KEY;
const SNYK_ORG_ID = process.env.SNYK_ORG_ID; // Optional default org ID from settings
if (!SNYK_API_KEY) {
console.error("SNYK_API_KEY environment variable is not set");
process.exit(1);
}
// Helper function to check if Snyk CLI is installed
function isSnykCliInstalled(): boolean {
try {
execSync('snyk --version', { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}
// Helper function to get org ID from Snyk CLI
function getOrgIdFromCli(): string | null {
try {
const output = execSync('snyk config get org', { encoding: 'utf8' }).trim();
if (output && output !== 'undefined' && output !== 'null') {
console.error('Retrieved organisation ID from Snyk CLI configuration');
return output;
}
} catch (error) {
console.error('Failed to get organisation ID from Snyk CLI:', error instanceof Error ? error.message : String(error));
}
return null;
}
// Schema definitions
const ScanRepoSchema = z.object({
url: z.string().url().describe('GitHub/GitLab repository URL (e.g., https://github.com/owner/repo)'),
branch: z.string().optional().describe('Branch to scan (optional)'),
org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
});
const ScanProjectSchema = z.object({
projectId: z.string().describe('Snyk project ID to scan'),
org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
});
const ListProjectsSchema = z.object({
org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
});
const VerifyTokenSchema = z.object({});
// Helper function to get org ID
function getOrgId(providedOrgId?: string): string {
// First try the provided org ID
if (providedOrgId) {
return providedOrgId;
}
// Then try the environment variable
if (SNYK_ORG_ID) {
return SNYK_ORG_ID;
}
// Finally, try to get it from the Snyk CLI if installed
if (isSnykCliInstalled()) {
const cliOrgId = getOrgIdFromCli();
if (cliOrgId) {
return cliOrgId;
}
}
throw new Error(
'Snyk organisation ID is required. You can provide it in one of these ways:\n' +
'1. Include it in the command\n' +
'2. Configure SNYK_ORG_ID in the MCP settings\n' +
'3. Set it in your Snyk CLI configuration using "snyk config set org=<org-id>"'
);
}
// Helper function to execute Snyk CLI commands
function executeSnykCommand(command: string, args: string[] = []): string {
try {
const fullCommand = command
? `snyk ${command} ${args.join(' ')}`
: `snyk ${args.join(' ')}`;
console.error('Executing command:', fullCommand);
// Execute the command and capture both stdout and stderr
return execSync(fullCommand, { encoding: 'utf8' });
} catch (error) {
if (error instanceof Error && 'stdout' in error) {
// If the command failed but returned output, return that output
return (error as any).stdout || (error as any).stderr || error.message;
}
throw error;
}
}
const server = new Server(
{ name: 'snyk-mcp-server', version: '1.0.0' },
{
capabilities: {
tools: {}
}
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "scan_repository",
description: "Scan a GitHub/GitLab repository for security vulnerabilities using Snyk. Requires the repository's URL (e.g., https://github.com/owner/repo). Do not use local file paths.",
inputSchema: zodToJsonSchema(ScanRepoSchema)
},
{
name: "scan_project",
description: "Scan an existing Snyk project",
inputSchema: zodToJsonSchema(ScanProjectSchema)
},
{
name: "list_projects",
description: "List all projects in a Snyk organisation",
inputSchema: zodToJsonSchema(ListProjectsSchema)
},
{
name: "verify_token",
description: "Verify that the configured Snyk token is valid",
inputSchema: zodToJsonSchema(VerifyTokenSchema)
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (!request.params.arguments) {
throw new Error("Arguments are required");
}
switch (request.params.name) {
case "verify_token": {
try {
// Use whoami to verify the token
const output = executeSnykCommand('whoami');
return {
content: [{
type: "text",
text: `✅ Token verified successfully!\n${output}`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `❌ Token verification failed: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
case "scan_repository": {
const args = ScanRepoSchema.parse(request.params.arguments);
const orgId = getOrgId(args.org);
try {
// Extract owner/repo from GitHub URL
const repoPath = args.url.split('github.com/')[1];
if (!repoPath) {
throw new Error('Invalid GitHub URL format');
}
// Use snyk code test with GitHub repository path
const cliArgs = [
'code',
'test',
'--org=' + orgId,
'--json',
'github.com/' + repoPath
];
if (args.branch) {
cliArgs.push('--branch=' + args.branch);
}
const output = executeSnykCommand('', cliArgs);
return {
content: [{
type: "text",
text: output
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to scan repository: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
case "scan_project": {
const args = ScanProjectSchema.parse(request.params.arguments);
const orgId = getOrgId(args.org);
try {
// Use snyk test to scan the project
const output = executeSnykCommand('test', [
'--org=' + orgId,
'--project-id=' + args.projectId,
'--json'
]);
return {
content: [{
type: "text",
text: output
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to scan project: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
case "list_projects": {
const args = ListProjectsSchema.parse(request.params.arguments);
const orgId = getOrgId(args.org);
try {
// Use snyk projects to list all projects
const output = executeSnykCommand('projects', [
'list',
'--org=' + orgId,
'--json'
]);
return {
content: [{
type: "text",
text: output
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to list projects: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
}
throw error;
}
});
const transport = new StdioServerTransport();
server.connect(transport).catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
console.error('Snyk MCP Server running on stdio');
```