#
tokens: 4434/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .github
│   └── workflows
│       └── build.yml
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | node_modules/
 2 | dist/
 3 | .vscode/
 4 | .venv/
 5 | venv/
 6 | __pycache__/
 7 | *.pyc
 8 | *.tmp
 9 | *.log
10 | *.swp
11 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Snyk MCP Server
 2 | 
 3 | A standalone Model Context Protocol server for Snyk security scanning functionality.
 4 | 
 5 | **WARNING: THIS MCP SERVER IS CURRENTLY IN ALPHA AND IS NOT YET FINISHED!**
 6 | 
 7 | ## Configuration
 8 | 
 9 | Update your Claude desktop config (`claude-config.json`):
10 | 
11 | ```json
12 | {
13 |   "mcpServers": {
14 |     "snyk": {
15 |       "command": "npx",
16 |       "args": [
17 |         "-y",
18 |         "github:sammcj/mcp-snyk"
19 |       ],
20 |       "env": {
21 |         "SNYK_API_KEY": "your_snyk_token",
22 |         "SNYK_ORG_ID": "your_default_org_id"  // Optional: Configure a default organisation ID
23 |       }
24 |     }
25 |   }
26 | }
27 | ```
28 | 
29 | Replace the token with your actual Snyk API token. The organisation ID can be configured in multiple ways:
30 | 
31 | 1. In the MCP settings via `SNYK_ORG_ID` (as shown above)
32 | 2. Using the Snyk CLI: `snyk config set org=your-org-id`
33 | 3. Providing it directly in commands
34 | 
35 | The server will try these methods in order until it finds a valid organisation ID.
36 | 
37 | ### Verifying Configuration
38 | 
39 | You can verify your Snyk token is configured correctly by asking Claude to run the verify_token command:
40 | 
41 | ```
42 | Verify my Snyk token configuration
43 | ```
44 | 
45 | 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.
46 | 
47 | ## Features
48 | 
49 | - Repository security scanning using GitHub/GitLab URLs
50 | - Snyk project scanning
51 | - Integration with Claude desktop
52 | - Token verification
53 | - Multiple organization ID configuration options
54 | - Snyk CLI integration for organization ID lookup
55 | 
56 | ## Usage
57 | 
58 | To scan a repository, you must provide its GitHub or GitLab URL:
59 | 
60 | ```
61 | Scan repository https://github.com/owner/repo for security vulnerabilities
62 | ```
63 | 
64 | 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.
65 | 
66 | For Snyk projects:
67 | 
68 | ```
69 | Scan Snyk project project-id-here
70 | ```
71 | 
72 | ### Organization ID Configuration
73 | 
74 | The server will look for the organization ID in this order:
75 | 
76 | 1. Command argument (if provided)
77 | 2. MCP settings environment variable (`SNYK_ORG_ID`)
78 | 3. Snyk CLI configuration (`snyk config get org`)
79 | 
80 | You only need to specify the organization ID in your command if you want to override the configured values:
81 | 
82 | ```
83 | Scan repository https://github.com/owner/repo in organisation org-id-here
84 | ```
85 | 
86 | ### Snyk CLI Integration
87 | 
88 | If you have the Snyk CLI installed (`npm install -g snyk`), the server can use it to:
89 | 
90 | - Get your default organisation ID
91 | - Fall back to CLI configuration when MCP settings are not provided
92 | - Show CLI configuration details in token verification output
93 | 
94 | This integration makes it easier to use the same organisation ID across both CLI and MCP server usage.
95 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "ES2020",
 5 |     "moduleResolution": "Bundler",
 6 |     "strict": true,
 7 |     "outDir": "dist",
 8 |     "skipLibCheck": true
 9 |   },
10 |   "include": ["src"]
11 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@sammcj/mcp-server-snyk",
 3 |   "version": "1.0.0",
 4 |   "type": "module",
 5 |   "main": "dist/index.js",
 6 |   "bin": "dist/index.js",
 7 |   "scripts": {
 8 |     "build": "tsc",
 9 |     "dev": "tsx watch src/index.ts"
10 |   },
11 |   "dependencies": {
12 |     "@modelcontextprotocol/sdk": "^1.5.0",
13 |     "node-fetch": "^3.3.2",
14 |     "zod": "^3.24.2",
15 |     "zod-to-json-schema": "^3.24.1"
16 |   },
17 |   "devDependencies": {
18 |     "@types/node": "^22.13.1",
19 |     "tsx": "^4.19.2",
20 |     "typescript": "^5.7.3"
21 |   }
22 | }
23 | 
```

--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Build
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |   pull_request:
 7 |     branches: [ main ]
 8 | 
 9 | permissions:
10 |   contents: write
11 | 
12 | jobs:
13 |   build:
14 |     runs-on: ubuntu-latest
15 | 
16 |     steps:
17 |     - uses: actions/checkout@v4
18 | 
19 |     - name: Use Node.js
20 |       uses: actions/setup-node@v4
21 |       with:
22 |         node-version: '22.x'
23 | 
24 |     - name: Install dependencies
25 |       run: npm install
26 | 
27 |     - name: Build
28 |       run: npm run build
29 | 
30 |     - name: Commit dist
31 |       run: |
32 |         git config --local user.email "github-actions[bot]@users.noreply.github.com"
33 |         git config --local user.name "github-actions[bot]"
34 |         git add dist/
35 |         git commit -m "Add built files" || echo "No changes to commit"
36 |         git push
37 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
  5 | import { z } from 'zod';
  6 | import { zodToJsonSchema } from 'zod-to-json-schema';
  7 | import { execSync } from 'child_process';
  8 | 
  9 | const SNYK_API_KEY = process.env.SNYK_API_KEY;
 10 | const SNYK_ORG_ID = process.env.SNYK_ORG_ID; // Optional default org ID from settings
 11 | 
 12 | if (!SNYK_API_KEY) {
 13 |   console.error("SNYK_API_KEY environment variable is not set");
 14 |   process.exit(1);
 15 | }
 16 | 
 17 | // Helper function to check if Snyk CLI is installed
 18 | function isSnykCliInstalled(): boolean {
 19 |   try {
 20 |     execSync('snyk --version', { stdio: 'ignore' });
 21 |     return true;
 22 |   } catch (error) {
 23 |     return false;
 24 |   }
 25 | }
 26 | 
 27 | // Helper function to get org ID from Snyk CLI
 28 | function getOrgIdFromCli(): string | null {
 29 |   try {
 30 |     const output = execSync('snyk config get org', { encoding: 'utf8' }).trim();
 31 |     if (output && output !== 'undefined' && output !== 'null') {
 32 |       console.error('Retrieved organisation ID from Snyk CLI configuration');
 33 |       return output;
 34 |     }
 35 |   } catch (error) {
 36 |     console.error('Failed to get organisation ID from Snyk CLI:', error instanceof Error ? error.message : String(error));
 37 |   }
 38 |   return null;
 39 | }
 40 | 
 41 | // Schema definitions
 42 | const ScanRepoSchema = z.object({
 43 |   url: z.string().url().describe('GitHub/GitLab repository URL (e.g., https://github.com/owner/repo)'),
 44 |   branch: z.string().optional().describe('Branch to scan (optional)'),
 45 |   org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
 46 | });
 47 | 
 48 | const ScanProjectSchema = z.object({
 49 |   projectId: z.string().describe('Snyk project ID to scan'),
 50 |   org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
 51 | });
 52 | 
 53 | const ListProjectsSchema = z.object({
 54 |   org: z.string().optional().describe('Snyk organisation ID (optional if configured in settings or available via Snyk CLI)')
 55 | });
 56 | 
 57 | const VerifyTokenSchema = z.object({});
 58 | 
 59 | // Helper function to get org ID
 60 | function getOrgId(providedOrgId?: string): string {
 61 |   // First try the provided org ID
 62 |   if (providedOrgId) {
 63 |     return providedOrgId;
 64 |   }
 65 | 
 66 |   // Then try the environment variable
 67 |   if (SNYK_ORG_ID) {
 68 |     return SNYK_ORG_ID;
 69 |   }
 70 | 
 71 |   // Finally, try to get it from the Snyk CLI if installed
 72 |   if (isSnykCliInstalled()) {
 73 |     const cliOrgId = getOrgIdFromCli();
 74 |     if (cliOrgId) {
 75 |       return cliOrgId;
 76 |     }
 77 |   }
 78 | 
 79 |   throw new Error(
 80 |     'Snyk organisation ID is required. You can provide it in one of these ways:\n' +
 81 |     '1. Include it in the command\n' +
 82 |     '2. Configure SNYK_ORG_ID in the MCP settings\n' +
 83 |     '3. Set it in your Snyk CLI configuration using "snyk config set org=<org-id>"'
 84 |   );
 85 | }
 86 | 
 87 | // Helper function to execute Snyk CLI commands
 88 | function executeSnykCommand(command: string, args: string[] = []): string {
 89 |   try {
 90 |     const fullCommand = command
 91 |       ? `snyk ${command} ${args.join(' ')}`
 92 |       : `snyk ${args.join(' ')}`;
 93 | 
 94 |     console.error('Executing command:', fullCommand);
 95 | 
 96 |     // Execute the command and capture both stdout and stderr
 97 |     return execSync(fullCommand, { encoding: 'utf8' });
 98 |   } catch (error) {
 99 |     if (error instanceof Error && 'stdout' in error) {
100 |       // If the command failed but returned output, return that output
101 |       return (error as any).stdout || (error as any).stderr || error.message;
102 |     }
103 |     throw error;
104 |   }
105 | }
106 | 
107 | const server = new Server(
108 |   { name: 'snyk-mcp-server', version: '1.0.0' },
109 |   {
110 |     capabilities: {
111 |       tools: {}
112 |     }
113 |   }
114 | );
115 | 
116 | server.setRequestHandler(ListToolsRequestSchema, async () => {
117 |   return {
118 |     tools: [
119 |       {
120 |         name: "scan_repository",
121 |         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.",
122 |         inputSchema: zodToJsonSchema(ScanRepoSchema)
123 |       },
124 |       {
125 |         name: "scan_project",
126 |         description: "Scan an existing Snyk project",
127 |         inputSchema: zodToJsonSchema(ScanProjectSchema)
128 |       },
129 |       {
130 |         name: "list_projects",
131 |         description: "List all projects in a Snyk organisation",
132 |         inputSchema: zodToJsonSchema(ListProjectsSchema)
133 |       },
134 |       {
135 |         name: "verify_token",
136 |         description: "Verify that the configured Snyk token is valid",
137 |         inputSchema: zodToJsonSchema(VerifyTokenSchema)
138 |       }
139 |     ]
140 |   };
141 | });
142 | 
143 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
144 |   try {
145 |     if (!request.params.arguments) {
146 |       throw new Error("Arguments are required");
147 |     }
148 | 
149 |     switch (request.params.name) {
150 |       case "verify_token": {
151 |         try {
152 |           // Use whoami to verify the token
153 |           const output = executeSnykCommand('whoami');
154 |           return {
155 |             content: [{
156 |               type: "text",
157 |               text: `✅ Token verified successfully!\n${output}`
158 |             }]
159 |           };
160 |         } catch (error) {
161 |           return {
162 |             content: [{
163 |               type: "text",
164 |               text: `❌ Token verification failed: ${error instanceof Error ? error.message : String(error)}`
165 |             }],
166 |             isError: true
167 |           };
168 |         }
169 |       }
170 | 
171 |       case "scan_repository": {
172 |         const args = ScanRepoSchema.parse(request.params.arguments);
173 |         const orgId = getOrgId(args.org);
174 | 
175 |         try {
176 |           // Extract owner/repo from GitHub URL
177 |           const repoPath = args.url.split('github.com/')[1];
178 |           if (!repoPath) {
179 |             throw new Error('Invalid GitHub URL format');
180 |           }
181 | 
182 |           // Use snyk code test with GitHub repository path
183 |           const cliArgs = [
184 |             'code',
185 |             'test',
186 |             '--org=' + orgId,
187 |             '--json',
188 |             'github.com/' + repoPath
189 |           ];
190 | 
191 |           if (args.branch) {
192 |             cliArgs.push('--branch=' + args.branch);
193 |           }
194 | 
195 |           const output = executeSnykCommand('', cliArgs);
196 |           return {
197 |             content: [{
198 |               type: "text",
199 |               text: output
200 |             }]
201 |           };
202 |         } catch (error) {
203 |           return {
204 |             content: [{
205 |               type: "text",
206 |               text: `Failed to scan repository: ${error instanceof Error ? error.message : String(error)}`
207 |             }],
208 |             isError: true
209 |           };
210 |         }
211 |       }
212 | 
213 |       case "scan_project": {
214 |         const args = ScanProjectSchema.parse(request.params.arguments);
215 |         const orgId = getOrgId(args.org);
216 | 
217 |         try {
218 |           // Use snyk test to scan the project
219 |           const output = executeSnykCommand('test', [
220 |             '--org=' + orgId,
221 |             '--project-id=' + args.projectId,
222 |             '--json'
223 |           ]);
224 |           return {
225 |             content: [{
226 |               type: "text",
227 |               text: output
228 |             }]
229 |           };
230 |         } catch (error) {
231 |           return {
232 |             content: [{
233 |               type: "text",
234 |               text: `Failed to scan project: ${error instanceof Error ? error.message : String(error)}`
235 |             }],
236 |             isError: true
237 |           };
238 |         }
239 |       }
240 | 
241 |       case "list_projects": {
242 |         const args = ListProjectsSchema.parse(request.params.arguments);
243 |         const orgId = getOrgId(args.org);
244 | 
245 |         try {
246 |           // Use snyk projects to list all projects
247 |           const output = executeSnykCommand('projects', [
248 |             'list',
249 |             '--org=' + orgId,
250 |             '--json'
251 |           ]);
252 |           return {
253 |             content: [{
254 |               type: "text",
255 |               text: output
256 |             }]
257 |           };
258 |         } catch (error) {
259 |           return {
260 |             content: [{
261 |               type: "text",
262 |               text: `Failed to list projects: ${error instanceof Error ? error.message : String(error)}`
263 |             }],
264 |             isError: true
265 |           };
266 |         }
267 |       }
268 | 
269 |       default:
270 |         throw new Error(`Unknown tool: ${request.params.name}`);
271 |     }
272 |   } catch (error) {
273 |     if (error instanceof z.ZodError) {
274 |       throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
275 |     }
276 |     throw error;
277 |   }
278 | });
279 | 
280 | const transport = new StdioServerTransport();
281 | server.connect(transport).catch((error) => {
282 |   console.error("Fatal error:", error);
283 |   process.exit(1);
284 | });
285 | 
286 | console.error('Snyk MCP Server running on stdio');
287 | 
```