diff --git a/.gitignore b/.gitignore
index ee0dab6e3..7113bfb00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,6 @@ snyk-linux
# Configuration #
#################################
snyk.config.local.json
+
+# Snyk Security Extension - AI Rules (auto-generated)
+.cursor/rules/snyk_rules.mdc
diff --git a/.windsurf/rules/snyk_rules.md b/.windsurf/rules/snyk_rules.md
new file mode 100644
index 000000000..1257850c0
--- /dev/null
+++ b/.windsurf/rules/snyk_rules.md
@@ -0,0 +1,13 @@
+---
+trigger: model_decision
+description: Snyk Security At Inception
+---
+
+# Project security best practices
+
+- Always run Snyk Code scanning tool for new first party code generated.
+- Always run Snyk SCA scanning tool for new dependencies or dependency updates.
+- Always run Snyk IaC scanning tool for new Infrastructure as Code updates.
+- If any security issues are found based on newly introduced or modified code or dependencies, attempt to fix the issues using the results context from Snyk.
+- Rescan the code after fixing the issues to ensure that the issues were fixed and that there are no newly introduced issues.
+- Repeat this process until no issues are found.
\ No newline at end of file
diff --git a/mcp_architecture_comparison.mmd b/mcp_architecture_comparison.mmd
new file mode 100644
index 000000000..1d5320618
--- /dev/null
+++ b/mcp_architecture_comparison.mmd
@@ -0,0 +1,61 @@
+%% MCP Architecture: Current vs Target
+
+graph TB
+ subgraph "Current Architecture"
+ direction TB
+ A1[User Changes Config] --> B1[Configuration Watcher]
+ B1 --> C1[handleSecurityAtInceptionChange]
+ C1 --> D1[Send Analytics to LS via command]
+ C1 --> E1[configureMcpHosts]
+ E1 --> F1{Detect IDE}
+ F1 -->|VS Code| G1[configureCopilot]
+ F1 -->|Cursor| H1[configureCursor]
+ F1 -->|Windsurf| I1[configureWindsurf]
+ G1 --> J1[Register MCP Provider]
+ G1 --> K1[Write Rules]
+ H1 --> L1[Update mcp.json]
+ H1 --> M1[Write Rules]
+ I1 --> N1[Update mcp_config.json]
+ I1 --> O1[Write Rules]
+ end
+
+ subgraph "Target Architecture"
+ direction TB
+ A2[User Changes Config] --> B2[Send to Language Server]
+ B2 --> C2[LS: UpdateSettings]
+ C2 --> D2[LS: Detect MCP Config Change]
+ D2 --> E2[LS: Send Analytics Automatically]
+ D2 --> F2[LS: Build MCP Config]
+ F2 --> G2[LS: Send $/snyk.configureSnykMCP]
+ G2 --> H2[Extension: Receive Notification]
+ H2 --> I2{IDE Type from Param}
+ I2 -->|VS Code| J2[configureCopilot]
+ I2 -->|Cursor| K2[configureCursor]
+ I2 -->|Windsurf| L2[configureWindsurf]
+ J2 --> M2[Register MCP Provider]
+ J2 --> N2[Write Rules]
+ K2 --> O2[Update mcp.json]
+ K2 --> P2[Write Rules]
+ L2 --> Q2[Update mcp_config.json]
+ L2 --> R2[Write Rules]
+ end
+
+ style C1 fill:#ffcdd2
+ style D1 fill:#ffcdd2
+ style E1 fill:#ffcdd2
+ style C2 fill:#c8e6c9
+ style D2 fill:#c8e6c9
+ style E2 fill:#c8e6c9
+ style F2 fill:#c8e6c9
+ style G2 fill:#c8e6c9
+
+ classDef current fill:#ffebee,stroke:#c62828
+ classDef target fill:#e8f5e9,stroke:#2e7d32
+ classDef removal fill:#ffcdd2,stroke:#c62828,stroke-width:3px
+ classDef addition fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
+
+ class A1,B1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1 current
+ class A2,B2,H2,I2,J2,K2,L2,M2,N2,O2,P2,Q2,R2 target
+ class C1,D1,E1 removal
+ class C2,D2,E2,F2,G2 addition
+
diff --git a/mcp_sequence_diagram.mmd b/mcp_sequence_diagram.mmd
new file mode 100644
index 000000000..84259b27e
--- /dev/null
+++ b/mcp_sequence_diagram.mmd
@@ -0,0 +1,58 @@
+%% MCP Configuration Sequence Diagram
+
+sequenceDiagram
+ participant User
+ participant IDE as IDE Extension
+ participant LS as Language Server
+ participant Analytics as Analytics Service
+ participant FileSystem as File System
+ participant VSCODE as VS Code API
+
+ User->>IDE: Changes MCP config setting
+ IDE->>LS: workspace/didChangeConfiguration
+
+ Note over LS: workspaceDidChangeConfiguration handler
+ LS->>LS: UpdateSettings(settings)
+
+ alt MCP config changed
+ LS->>LS: Detect autoConfigureMcpServer changed
+ LS->>Analytics: SendConfigChangedAnalytics("autoConfigureSnykMcpServer", oldValue, newValue, triggerSource)
+
+ LS->>LS: Detect secureAtInceptionExecutionFrequency changed
+ LS->>Analytics: SendConfigChangedAnalytics("secureAtInceptionExecutionFrequency", oldValue, newValue, triggerSource)
+
+ LS->>LS: configureMcp()
+ LS->>LS: Build MCP config (command, args, env)
+
+ Note over LS: Create SnykConfigureMcpParams:
- command: CLI path
- args: ["mcp", "-t", "stdio"]
- env: {SNYK_CFG_ORG, SNYK_API, ...}
- ideName: "vscode"|"cursor"|"windsurf"
+
+ LS->>IDE: $/snyk.configureSnykMCP notification
+ end
+
+ IDE->>IDE: handleMcpConfigNotification(params)
+
+ alt IDE is VS Code
+ IDE->>VSCODE: vscode.lm.registerMcpServerDefinitionProvider()
+ VSCODE-->>IDE: Provider registered
+ IDE->>FileSystem: Write .github/instructions/snyk_rules.instructions.md
+ FileSystem-->>IDE: Rules written
+ else IDE is Cursor
+ IDE->>FileSystem: Read ~/.cursor/mcp.json
+ FileSystem-->>IDE: Current config
+ IDE->>FileSystem: Write updated ~/.cursor/mcp.json
+ FileSystem-->>IDE: Config updated
+ IDE->>FileSystem: Write .cursor/rules/snyk_rules.mdc
+ FileSystem-->>IDE: Rules written
+ else IDE is Windsurf
+ IDE->>FileSystem: Read ~/.codeium/windsurf/mcp_config.json
+ FileSystem-->>IDE: Current config
+ IDE->>FileSystem: Write updated mcp_config.json
+ FileSystem-->>IDE: Config updated
+ IDE->>FileSystem: Write .windsurf/rules/snyk_rules.md
+ FileSystem-->>IDE: Rules written
+ end
+
+ IDE-->>User: MCP configured successfully
+
+ Note over IDE,LS: Analytics flow through LS
No special handling in extension
+
diff --git a/mcp_to_ls_implementation_plan.md b/mcp_to_ls_implementation_plan.md
new file mode 100644
index 000000000..4bb8264f4
--- /dev/null
+++ b/mcp_to_ls_implementation_plan.md
@@ -0,0 +1,350 @@
+# Implementation Plan: Move MCP Configuration to Language Server
+
+## Overview
+Move MCP (Model Context Protocol) configuration logic from the VS Code extension to the language server and refactor analytics handling to use the language server's existing infrastructure.
+
+## Goals
+1. Move MCP configuration logic from `vscode-extension/src/snyk/cli/mcp/mcp.ts` to the language server
+2. Remove special analytics handling in the configuration watcher and use language server's existing analytics infrastructure
+3. Implement custom notification `$/snyk.configureSnykMCP` for IDE-specific MCP configuration that requires VS Code APIs
+
+## Architecture
+
+### Current State
+- MCP configuration is handled entirely in the VS Code extension (`mcp.ts`)
+- Configuration changes trigger MCP reconfiguration via `handleSecurityAtInceptionChange`
+- Analytics for MCP config changes are sent from the extension using custom code
+- Extension directly modifies IDE-specific config files (Cursor, Windsurf, VS Code)
+
+### Target State
+- Language server determines MCP configuration needs based on settings
+- Language server sends `$/snyk.configureSnykMCP` notification with cmd, args, env
+- IDE extension listens for notification and configures MCP using IDE-specific APIs
+- Analytics for all config changes (including MCP) flow through language server's existing infrastructure
+
+## Implementation Phases
+
+### Phase 1: Planning ✓
+- [x] Analyze current MCP configuration logic
+- [x] Analyze current analytics flow
+- [x] Identify files and packages to modify
+- [x] Create implementation plan
+- [x] Get approval for implementation plan
+
+### Phase 2: Implementation (TDD)
+
+#### Step 1: Add MCP configuration settings to Language Server [COMPLETE]
+**Objective**: Add new configuration fields to the language server's Settings struct
+
+**Files to modify**:
+- `snyk-ls/internal/types/lsp.go`
+- `snyk-ls/application/config/config.go`
+- `snyk-ls/application/server/configuration.go`
+
+**Actions**:
+- [ ] Write tests for new configuration fields (in progress)
+ - Test parsing `autoConfigureSnykMcpServer` from settings
+ - Test parsing `secureAtInceptionExecutionFrequency` from settings
+ - Test configuration change detection
+- [ ] Add fields to `Settings` struct in `lsp.go`:
+ - `AutoConfigureSnykMcpServer string`
+ - `SecureAtInceptionExecutionFrequency string`
+- [ ] Add fields to `Config` struct in `config.go`:
+ - `autoConfigureSnykMcpServer bool`
+ - `secureAtInceptionExecutionFrequency string`
+- [ ] Add getters/setters with proper locking
+- [ ] Update `writeSettings` in `configuration.go` to handle new fields
+- [ ] Add analytics for config changes using `analytics.SendConfigChangedAnalytics`
+ - Call for `autoConfigureSnykMcpServer` changes
+ - Call for `secureAtInceptionExecutionFrequency` changes
+- [ ] Run tests: `make test`
+- [ ] Run linter: `make lint`
+- [ ] Fix any issues
+
+#### Step 2: Create MCP configuration notification type [IN PROGRESS]
+**Objective**: Define the notification structure for `$/snyk.configureSnykMCP`
+
+**Files to modify**:
+- `snyk-ls/internal/types/lsp.go`
+
+**Actions**:
+- [ ] Write tests for MCP configuration notification type (skipped)
+- [ ] Add `SnykConfigureMcpParams` struct:
+ ```go
+ type SnykConfigureMcpParams struct {
+ Command string `json:"command"`
+ Args []string `json:"args"`
+ Env map[string]string `json:"env"`
+ IdeName string `json:"ideName"` // "cursor", "windsurf", "vscode"
+ }
+ ```
+- [ ] Run tests: `make test`
+- [ ] Fix any issues
+
+#### Step 3: Implement MCP configuration logic in Language Server
+**Objective**: Move the MCP configuration logic from extension to LS
+
+**Files to create**:
+- `snyk-ls/application/config/mcp_config.go`
+- `snyk-ls/application/config/mcp_config_test.go`
+
+**Actions**:
+- [ ] Write comprehensive tests for MCP configuration logic
+ - Test detecting IDE type from integration environment
+ - Test building MCP command and args
+ - Test building MCP environment variables
+ - Test triggering notification when config changes
+- [ ] Create `mcp_config.go` with functions:
+ - `shouldConfigureMcp(c *Config) bool` - check if MCP should be configured
+ - `getMcpCommand(c *Config) string` - get CLI path
+ - `getMcpArgs() []string` - return `["mcp", "-t", "stdio"]`
+ - `getMcpEnv(c *Config) map[string]string` - build env vars (SNYK_CFG_ORG, SNYK_API, IDE_CONFIG_PATH, TRUSTED_FOLDERS)
+ - `getIdeName(c *Config) string` - determine IDE from integration environment
+ - `configureMcp(c *Config)` - main function to trigger configuration
+- [ ] Integrate `configureMcp` into `UpdateSettings` in `configuration.go`
+- [ ] Send analytics for MCP config changes using `analytics.SendConfigChangedAnalytics`
+ - Example: `analytics.SendConfigChangedAnalytics(c, "autoConfigureSnykMcpServer", oldValue, newValue, triggerSource)`
+ - Example: `analytics.SendConfigChangedAnalytics(c, "secureAtInceptionExecutionFrequency", oldValue, newValue, triggerSource)`
+- [ ] Run tests: `make test`
+- [ ] Run linter: `make lint`
+- [ ] Fix any issues
+
+#### Step 4: Register MCP configuration notification handler
+**Objective**: Add notification handler to send MCP config to IDE
+
+**Files to modify**:
+- `snyk-ls/application/server/notification.go`
+
+**Actions**:
+- [ ] Write tests for notification registration
+- [ ] Add case in `registerNotifier` switch statement:
+ ```go
+ case types.SnykConfigureMcpParams:
+ notifier(c, srv, "$/snyk.configureSnykMCP", params)
+ logger.Debug().Interface("mcpConfig", params).Msg("sending MCP config to client")
+ ```
+- [ ] Update `configureMcp` to send notification via `di.Notifier().Send()`
+- [ ] Run tests: `make test`
+- [ ] Fix any issues
+
+#### Step 5: Update VS Code extension to handle MCP notification
+**Objective**: Listen for `$/snyk.configureSnykMCP` and configure MCP
+
+**Files to modify**:
+- `vscode-extension/src/snyk/common/languageServer/languageServer.ts`
+- `vscode-extension/src/snyk/cli/mcp/mcp.ts`
+
+**Actions**:
+- [ ] Write tests for notification handler
+- [ ] Create interface for MCP config params in `languageServer.ts`:
+ ```typescript
+ interface McpConfigParams {
+ command: string;
+ args: string[];
+ env: Record;
+ ideName: string;
+ }
+ ```
+- [ ] Register notification handler in `LanguageServer` class
+- [ ] Refactor `mcp.ts`:
+ - Keep IDE-specific configuration functions
+ - Remove configuration change detection (now in LS)
+ - Remove analytics sending (now in LS)
+ - Create new function `handleMcpConfigNotification(params: McpConfigParams)`
+ - Delegate to appropriate IDE-specific function based on `ideName`
+- [ ] Run tests: `npm run test:unit`
+- [ ] Run tests: `npm run test:integration`
+- [ ] Run linter: `npm run lint:fix`
+- [ ] Fix any issues
+
+#### Step 6: Remove redundant analytics code from extension
+**Objective**: Remove special analytics handling for MCP config changes
+
+**Files to modify**:
+- `vscode-extension/src/snyk/common/configuration/securityAtInceptionHandler.ts`
+- `vscode-extension/src/snyk/common/watchers/configurationWatcher.ts`
+
+**Actions**:
+- [ ] Write/update tests for configuration handling
+- [ ] In `securityAtInceptionHandler.ts`:
+ - Remove `sendConfigChangedAnalytics` function (lines 71-88)
+ - Remove calls to `sendConfigChangedAnalytics` (lines 42-49, 58-65)
+ - Keep memento state tracking
+ - Simplify to only call `configureMcpHosts` when needed
+- [ ] In `configurationWatcher.ts`:
+ - Configuration change handling remains the same
+ - Analytics will now be sent by LS automatically
+- [ ] Run tests: `npm run test:unit`
+- [ ] Run linter: `npm run lint:fix`
+- [ ] Fix any issues
+
+#### Step 7: Add configuration settings to extension settings sync
+**Objective**: Ensure new settings are synced to LS
+
+**Files to check/modify**:
+- `vscode-extension/src/snyk/common/languageServer/languageServer.ts`
+
+**Actions**:
+- [ ] Verify settings sync includes MCP configuration fields
+- [ ] Add if missing: `autoConfigureSnykMcpServer` and `secureAtInceptionExecutionFrequency`
+- [ ] Run tests: `npm run test:unit`
+- [ ] Fix any issues
+
+#### Step 8: Update documentation
+**Objective**: Document the new MCP configuration flow
+
+**Files to create/modify**:
+- `snyk-ls/docs/mcp-configuration.md`
+- `vscode-extension/docs/` (if exists)
+
+**Actions**:
+- [ ] Create mermaid diagram for MCP configuration flow
+- [ ] Document the notification protocol
+- [ ] Document IDE-specific configuration requirements
+- [ ] Run `make generate-diagrams` (for snyk-ls)
+- [ ] Add to implementation plan
+
+### Phase 3: Review
+
+#### Step 1: Code Review
+- [ ] Self-review all changes
+- [ ] Ensure all tests pass
+- [ ] Ensure no new linting errors
+- [ ] Verify test coverage >= 80%
+
+#### Step 2: Integration Testing
+- [ ] Test MCP configuration in VS Code
+- [ ] Test MCP configuration in Cursor
+- [ ] Test MCP configuration in Windsurf
+- [ ] Test configuration changes trigger reconfiguration
+- [ ] Test analytics are sent correctly
+- [ ] Verify rules publishing still works
+
+#### Step 3: Security Scanning
+- [ ] Run `snyk_code_scan` on both repositories
+- [ ] Run `snyk_sca_scan` on both repositories (if dependencies changed)
+- [ ] Fix any security issues found
+
+#### Step 4: Final Cleanup
+- [ ] Remove any temporary test files
+- [ ] Update CHANGELOG entries
+- [ ] Verify no implementation plan files are staged for commit
+
+## Progress Tracking
+
+### Current Status
+- Phase: Implementation (Phase 2)
+- Step: Step 1 - Add MCP configuration settings to Language Server
+- Next: Write tests for new configuration fields
+
+### Completed Steps
+- [x] Initial analysis
+- [x] Implementation plan creation
+- [x] Plan approval received
+
+### In Progress
+- [ ] Phase 2, Step 1: Add MCP configuration settings to Language Server
+
+### Blocked
+- None
+
+## Technical Details
+
+### MCP Environment Variables
+```typescript
+// Built by language server, sent to IDE
+{
+ SNYK_CFG_ORG?: string, // From config.organization
+ SNYK_API?: string, // From config.snykApiEndpoint
+ IDE_CONFIG_PATH?: string, // IDE name from integration environment
+ TRUSTED_FOLDERS?: string // Semicolon-separated trusted folders
+}
+```
+
+### Notification Flow
+```
+Configuration Change
+ ↓
+LS: workspace/didChangeConfiguration handler
+ ↓
+LS: UpdateSettings
+ ↓
+LS: Detect MCP config change
+ ↓
+LS: Send analytics (using existing infrastructure)
+ ↓
+LS: Send $/snyk.configureSnykMCP notification
+ ↓
+IDE: Receive notification
+ ↓
+IDE: Call IDE-specific configuration function
+ ↓
+IDE: Configure MCP (file writes, API calls, etc.)
+```
+
+### Files Summary
+
+**Language Server (snyk-ls)**:
+- `internal/types/lsp.go` - Add Settings fields and notification type
+- `application/config/config.go` - Add config fields and methods
+- `application/config/mcp_config.go` - New: MCP configuration logic
+- `application/config/mcp_config_test.go` - New: Tests
+- `application/server/configuration.go` - Update settings handler
+- `application/server/notification.go` - Register notification
+- `docs/mcp-configuration.md` - New: Documentation
+
+**VS Code Extension (vscode-extension)**:
+- `src/snyk/common/languageServer/languageServer.ts` - Add notification handler
+- `src/snyk/cli/mcp/mcp.ts` - Refactor to handle notifications
+- `src/snyk/common/configuration/securityAtInceptionHandler.ts` - Remove analytics
+- `src/snyk/common/watchers/configurationWatcher.ts` - Simplify (analytics now in LS)
+
+## Testing Strategy
+
+### Unit Tests
+- **Language Server**:
+ - Configuration field parsing
+ - MCP environment building
+ - MCP command/args generation
+ - IDE name detection
+ - Notification sending
+
+- **Extension**:
+ - Notification handler registration
+ - MCP configuration delegation
+ - IDE-specific configuration (with mocks)
+
+### Integration Tests
+- **Language Server**:
+ - Configuration change triggers MCP notification
+ - Analytics sent on config change
+
+- **Extension**:
+ - Notification received triggers configuration
+ - Configuration changes persist correctly
+
+### E2E Tests (Manual)
+- Test in VS Code with Copilot
+- Test in Cursor
+- Test in Windsurf
+- Verify MCP servers appear correctly
+- Verify rules are published correctly
+
+## Commit Strategy
+
+Atomic commits for each step:
+1. `feat: add MCP configuration settings to language server [ISSUE-ID]`
+2. `feat: add MCP configuration notification type [ISSUE-ID]`
+3. `feat: implement MCP configuration logic in language server [ISSUE-ID]`
+4. `feat: register MCP configuration notification handler [ISSUE-ID]`
+5. `feat: add MCP notification handler to VS Code extension [ISSUE-ID]`
+6. `refactor: remove redundant MCP analytics from extension [ISSUE-ID]`
+7. `chore: update MCP configuration documentation [ISSUE-ID]`
+
+## Notes
+- No issue ID found in current branch (main)
+- This is a cross-repository change (snyk-ls + vscode-extension)
+- Must maintain backwards compatibility during transition
+- Analytics flow changes but analytics data remains the same
+- IDE-specific logic remains in extension (can't move file I/O and API calls to LS)
+
diff --git a/mcp_to_ls_implementation_plan.mmd b/mcp_to_ls_implementation_plan.mmd
new file mode 100644
index 000000000..aa450c853
--- /dev/null
+++ b/mcp_to_ls_implementation_plan.mmd
@@ -0,0 +1,49 @@
+%% MCP Configuration Flow
+
+graph TB
+ subgraph "VS Code Extension"
+ A[Configuration Change Detected] --> B[Send workspace/didChangeConfiguration]
+ N[Receive $/snyk.configureSnykMCP] --> O{IDE Type?}
+ O -->|VS Code| P[configureCopilot]
+ O -->|Cursor| Q[configureCursor]
+ O -->|Windsurf| R[configureWindsurf]
+ P --> S[Register MCP Provider]
+ P --> T[Write Rules Files]
+ Q --> U[Update mcp.json]
+ Q --> V[Write Rules Files]
+ R --> W[Update mcp_config.json]
+ R --> X[Write Rules Files]
+ end
+
+ subgraph "Language Server"
+ B --> C[workspaceDidChangeConfiguration Handler]
+ C --> D[UpdateSettings]
+ D --> E{MCP Config Changed?}
+ E -->|Yes| F[Send Analytics]
+ E -->|Yes| G[Build MCP Config]
+ E -->|No| H[Return]
+ F --> I[analytics.SendConfigChangedAnalytics]
+ G --> J[Get CLI Path]
+ G --> K[Build Args: mcp -t stdio]
+ G --> L[Build Env Variables]
+ J --> M[Create SnykConfigureMcpParams]
+ K --> M
+ L --> M
+ M --> N
+ end
+
+ style A fill:#e1f5ff
+ style N fill:#fff4e1
+ style I fill:#e8f5e9
+ style M fill:#f3e5f5
+
+ classDef extensionNode fill:#e1f5ff,stroke:#01579b
+ classDef lsNode fill:#fff4e1,stroke:#e65100
+ classDef analyticsNode fill:#e8f5e9,stroke:#1b5e20
+ classDef notificationNode fill:#f3e5f5,stroke:#4a148c
+
+ class A,O,P,Q,R,S,T,U,V,W,X extensionNode
+ class C,D,E,G,H,J,K,L lsNode
+ class F,I analyticsNode
+ class M,N notificationNode
+
diff --git a/src/snyk/cli/mcp/mcp.ts b/src/snyk/cli/mcp/mcp.ts
deleted file mode 100644
index 9e725fe8c..000000000
--- a/src/snyk/cli/mcp/mcp.ts
+++ /dev/null
@@ -1,327 +0,0 @@
-import * as vscode from 'vscode';
-import { IConfiguration } from '../../common/configuration/configuration';
-import { Logger } from '../../common/logger/logger';
-import * as fs from 'fs';
-import * as os from 'os';
-import path from 'path';
-
-type Env = Record;
-interface McpServer {
- command: string;
- args: string[];
- env: Env;
-}
-interface McpConfig {
- mcpServers: Record;
-}
-
-const SERVER_KEY = 'Snyk';
-
-export async function configureMcpHosts(vscodeContext: vscode.ExtensionContext, configuration: IConfiguration) {
- const appName = vscode.env.appName.toLowerCase();
- const isWindsurf = appName.includes('windsurf');
- const isCursor = appName.includes('cursor');
- const isVsCode = appName.includes('visual studio code');
-
- if (isCursor) {
- await configureCursor(vscodeContext, configuration);
- return;
- }
- if (isWindsurf) {
- await configureWindsurf(vscodeContext, configuration);
- return;
- }
- if (isVsCode) {
- await configureCopilot(vscodeContext, configuration);
- return;
- }
-}
-
-export async function configureCopilot(vscodeContext: vscode.ExtensionContext, configuration: IConfiguration) {
- const autoConfigureMcpServer = configuration.getAutoConfigureMcpServer();
- const secureAtInceptionExecutionFrequency = configuration.getSecureAtInceptionExecutionFrequency();
- try {
- if (autoConfigureMcpServer) {
- vscodeContext.subscriptions.push(
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
- /* eslint-disable @typescript-eslint/no-unsafe-call */
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
- // @ts-expect-error backward compatibility for older VS Code versions
- vscode.lm.registerMcpServerDefinitionProvider('snyk-security-scanner', {
- onDidChangeMcpServerDefinitions: new vscode.EventEmitter().event,
- provideMcpServerDefinitions: async () => {
- // @ts-expect-error backward compatibility for older VS Code versions
- const output: vscode.McpServerDefinition[][] = [];
-
- /* eslint-disable @typescript-eslint/no-unsafe-call */
- const cliPath = await configuration.getCliPath();
- /* eslint-disable @typescript-eslint/no-unsafe-return */
- const args = ['mcp', '-t', 'stdio'];
- const snykEnv = getSnykMcpEnv(configuration);
- const processEnv: Env = {};
- Object.entries(process.env).forEach(([key, value]) => {
- processEnv[key] = value ?? '';
- });
- const env: Env = { ...processEnv, ...snykEnv };
-
- // @ts-expect-error backward compatibility for older VS Code versions
- output.push(new vscode.McpStdioServerDefinition(SERVER_KEY, cliPath, args, env));
-
- return output;
- },
- }),
- );
- }
- } catch (err) {
- Logger.debug(
- `VS Code MCP Server Definition Provider API is not available. This feature requires VS Code version > 1.101.0.`,
- );
- }
-
- // Rules publishing for Copilot
- const filePath = path.join('.github', 'instructions', 'snyk_rules.instructions.md');
- try {
- if (secureAtInceptionExecutionFrequency === 'Manual') {
- // Delete rules from project
- await deleteLocalRulesForIde(filePath);
- return;
- }
- const rulesContent = await readBundledRules(vscodeContext, secureAtInceptionExecutionFrequency);
- await writeLocalRulesForIde(filePath, rulesContent);
- await ensureInGitignore([filePath]);
- } catch {
- Logger.error('Failed to publish Copilot rules');
- }
-}
-
-export async function configureWindsurf(vscodeContext: vscode.ExtensionContext, configuration: IConfiguration) {
- const autoConfigureMcpServer = configuration.getAutoConfigureMcpServer();
- const secureAtInceptionExecutionFrequency = configuration.getSecureAtInceptionExecutionFrequency();
- try {
- if (autoConfigureMcpServer) {
- const baseDir = path.join(os.homedir(), '.codeium', 'windsurf');
- const configPath = path.join(baseDir, 'mcp_config.json');
- if (!fs.existsSync(baseDir)) {
- Logger.debug(`Windsurf base directory not found at ${baseDir}, skipping MCP configuration.`);
- } else {
- const cliPath = await configuration.getCliPath();
- const env = getSnykMcpEnv(configuration);
- await ensureMcpServerInJson(configPath, SERVER_KEY, cliPath, ['mcp', '-t', 'stdio'], env);
- Logger.debug(`Ensured Windsurf MCP config at ${configPath}`);
- }
- }
- } catch {
- Logger.error('Failed to update Windsurf MCP config');
- }
-
- const localPath = path.join('.windsurf', 'rules', 'snyk_rules.md');
- try {
- if (secureAtInceptionExecutionFrequency === 'Manual') {
- // Delete rules from project
- await deleteLocalRulesForIde(localPath);
- return;
- }
- const rulesContent = await readBundledRules(vscodeContext, secureAtInceptionExecutionFrequency);
- await writeLocalRulesForIde(localPath, rulesContent);
- await ensureInGitignore([localPath]);
- } catch {
- Logger.error('Failed to publish Windsurf rules');
- }
-}
-
-export async function configureCursor(vscodeContext: vscode.ExtensionContext, configuration: IConfiguration) {
- const autoConfigureMcpServer = configuration.getAutoConfigureMcpServer();
- const secureAtInceptionExecutionFrequency = configuration.getSecureAtInceptionExecutionFrequency();
- try {
- if (autoConfigureMcpServer) {
- const configPath = path.join(os.homedir(), '.cursor', 'mcp.json');
- const cliPath = await configuration.getCliPath();
- const env = getSnykMcpEnv(configuration);
-
- await ensureMcpServerInJson(configPath, SERVER_KEY, cliPath, ['mcp', '-t', 'stdio'], env);
- Logger.debug(`Ensured Cursor MCP config at ${configPath}`);
- }
- } catch {
- Logger.error('Failed to update Cursor MCP config');
- }
-
- const cursorRulesPath = path.join('.cursor', 'rules', 'snyk_rules.mdc');
- try {
- if (secureAtInceptionExecutionFrequency === 'Manual') {
- // Delete rules from project (Cursor doesn't support global rules)
- await deleteLocalRulesForIde(cursorRulesPath);
- return;
- }
-
- const rulesContent = await readBundledRules(vscodeContext, secureAtInceptionExecutionFrequency);
- await writeLocalRulesForIde(cursorRulesPath, rulesContent);
- await ensureInGitignore([cursorRulesPath]);
- } catch {
- Logger.error('Failed to publish Cursor rules');
- }
-}
-
-async function ensureMcpServerInJson(
- filePath: string,
- serverKey: string,
- command: string,
- args: string[],
- env: Env,
-): Promise {
- let raw: unknown = undefined;
- if (fs.existsSync(filePath)) {
- try {
- raw = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
- } catch {
- // ignore parse error; will recreate minimal structure
- }
- }
- type RawConfig = { mcpServers?: Record };
- const config: McpConfig = { mcpServers: {} };
- if (raw && typeof raw === 'object' && raw !== null && Object.prototype.hasOwnProperty.call(raw, 'mcpServers')) {
- const servers = (raw as RawConfig).mcpServers;
- if (servers && typeof servers === 'object') {
- config.mcpServers = servers;
- }
- }
-
- const serverKeyLower = serverKey.toLowerCase();
- let matchedKey: string | undefined = undefined;
- for (const key of Object.keys(config.mcpServers)) {
- const lower = key.toLowerCase();
- if (lower === serverKeyLower || lower.includes(serverKeyLower)) {
- matchedKey = key;
- break;
- }
- }
- const keyToUse = matchedKey ?? serverKey;
- const existing = config.mcpServers[keyToUse];
- const desired: McpServer = { command, args, env };
-
- // Merge env: keep existing keys; add or override Snyk keys
- let resultingEnv: Env;
- if (existing && existing.env) {
- resultingEnv = { ...existing.env };
- const overrideKeys: (keyof Env)[] = ['SNYK_CFG_ORG', 'SNYK_API', 'IDE_CONFIG_PATH', 'TRUSTED_FOLDERS'];
- for (const k of overrideKeys) {
- if (typeof env[k] !== 'undefined') {
- resultingEnv[k] = env[k];
- }
- }
- } else {
- resultingEnv = { ...(env || {}) };
- }
-
- const needsWrite =
- !existing ||
- existing.command !== desired.command ||
- JSON.stringify(existing.args) !== JSON.stringify(desired.args) ||
- JSON.stringify(existing.env || {}) !== JSON.stringify(resultingEnv || {});
-
- if (!needsWrite) return;
-
- config.mcpServers[keyToUse] = { command, args, env: resultingEnv };
- await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
- await fs.promises.writeFile(filePath, JSON.stringify(config, null, 2), 'utf8');
-}
-
-async function readBundledRules(vsCodeContext: vscode.ExtensionContext, frequency: string): Promise {
- const rulesFileName = frequency === 'Smart Scan' ? 'snyk_rules_smart_apply.md' : 'snyk_rules_always_apply.md';
- return await fs.promises.readFile(path.join(vsCodeContext.extensionPath, 'out', 'assets', rulesFileName), 'utf8');
-}
-
-async function writeLocalRulesForIde(relativeRulesPath: string, rulesContent: string): Promise {
- const folders = vscode.workspace.workspaceFolders;
- if (!folders || folders.length === 0) {
- void vscode.window.showInformationMessage('No workspace folder found. Local rules require an open workspace.');
- return;
- }
- for (const folder of folders) {
- const root = folder.uri.fsPath;
- const rulesPath = path.join(root, relativeRulesPath);
- await fs.promises.mkdir(path.dirname(rulesPath), { recursive: true });
- let existing = '';
- try {
- existing = await fs.promises.readFile(rulesPath, 'utf8');
- } catch {
- // ignore
- }
- if (existing !== rulesContent) {
- await fs.promises.writeFile(rulesPath, rulesContent, 'utf8');
- Logger.debug(`Wrote local rules to ${rulesPath}`);
- } else {
- Logger.debug(`Local rules already up to date at ${rulesPath}.`);
- }
- }
-}
-
-async function deleteLocalRulesForIde(relativeRulesPath: string): Promise {
- const folders = vscode.workspace.workspaceFolders;
- if (!folders || folders.length === 0) {
- return;
- }
- for (const folder of folders) {
- const root = folder.uri.fsPath;
- const rulesPath = path.join(root, relativeRulesPath);
- try {
- if (fs.existsSync(rulesPath)) {
- await fs.promises.unlink(rulesPath);
- Logger.debug(`Deleted local rules from ${rulesPath}`);
- }
- } catch (err) {
- Logger.debug(`Failed to delete local rules from ${rulesPath}: ${err}`);
- }
- }
-}
-
-async function ensureInGitignore(patterns: string[]): Promise {
- const folders = vscode.workspace.workspaceFolders;
- if (!folders || folders.length === 0) {
- return;
- }
-
- await Promise.all(
- folders.map(async folder => {
- const gitignorePath = path.join(folder.uri.fsPath, '.gitignore');
- let content = '';
-
- try {
- content = await fs.promises.readFile(gitignorePath, 'utf8');
- } catch {
- Logger.debug(`.gitignore does not exist at ${gitignorePath}`);
- return;
- }
-
- // Split into lines handling both \n and \r\n
- const lines = content.split(/\r?\n/);
- const missing = patterns.filter(p => !lines.some(line => line.trim() === p.trim()));
-
- if (missing.length === 0) {
- Logger.debug(`Snyk rules already in .gitignore at ${gitignorePath}`);
- return;
- }
-
- const addition = `\n# Snyk Security Extension - AI Rules (auto-generated)\n${missing.join('\n')}\n`;
- const updated = content + addition;
- await fs.promises.writeFile(gitignorePath, updated, 'utf8');
- Logger.debug(`Added Snyk rules to .gitignore at ${gitignorePath}: ${missing.join(', ')}`);
- }),
- );
-}
-
-function getSnykMcpEnv(configuration: IConfiguration): Env {
- const env: Env = {};
- if (configuration.organization) {
- env.SNYK_CFG_ORG = configuration.organization;
- }
- if (configuration.snykApiEndpoint) {
- env.SNYK_API = configuration.snykApiEndpoint;
- }
- const trustedFolders = configuration.getTrustedFolders();
- if (trustedFolders.length > 0) {
- env.TRUSTED_FOLDERS = trustedFolders.join(';');
- }
- env.IDE_CONFIG_PATH = vscode.env.appName;
-
- return env;
-}
diff --git a/src/snyk/common/configuration/securityAtInceptionHandler.ts b/src/snyk/common/configuration/securityAtInceptionHandler.ts
deleted file mode 100644
index 18330662e..000000000
--- a/src/snyk/common/configuration/securityAtInceptionHandler.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import { IExtension } from '../../base/modules/interfaces';
-import { ILog } from '../logger/interfaces';
-import { configuration } from './instance';
-import { DEFAULT_SECURE_AT_INCEPTION_EXECUTION_FREQUENCY } from './configuration';
-import {
- MEMENTO_AUTO_CONFIGURE_MCP_SERVER,
- MEMENTO_SECURE_AT_INCEPTION_EXECUTION_FREQUENCY,
-} from '../constants/globalState';
-import { User } from '../user';
-import { AnalyticsSender } from '../analytics/AnalyticsSender';
-import { AnalyticsEvent } from '../analytics/AnalyticsEvent';
-import { vsCodeCommands } from '../vscode/commands';
-import { configureMcpHosts } from '../../cli/mcp/mcp';
-import * as vscode from 'vscode';
-
-export async function handleSecurityAtInceptionChange(
- extension: IExtension,
- logger: ILog,
- user: User,
- vscodeContext: vscode.ExtensionContext,
-): Promise {
- if (!extension.context) {
- return;
- }
-
- const currentAutoConfigureMcpServerConfig = configuration.getAutoConfigureMcpServer();
- const currentSecureAtInceptionExecutionFrequencyConfig = configuration.getSecureAtInceptionExecutionFrequency();
-
- const previousAutoConfigureMcpServerConfig =
- extension.context.getGlobalStateValue(MEMENTO_AUTO_CONFIGURE_MCP_SERVER) ?? false;
-
- const previousSecureAtInceptionExecutionFrequencyConfig =
- extension.context.getGlobalStateValue(MEMENTO_SECURE_AT_INCEPTION_EXECUTION_FREQUENCY) ??
- DEFAULT_SECURE_AT_INCEPTION_EXECUTION_FREQUENCY;
-
- if (currentAutoConfigureMcpServerConfig !== previousAutoConfigureMcpServerConfig) {
- await extension.context.updateGlobalStateValue(
- MEMENTO_AUTO_CONFIGURE_MCP_SERVER,
- currentAutoConfigureMcpServerConfig,
- );
-
- sendConfigChangedAnalytics(
- extension,
- logger,
- user,
- 'autoConfigureSnykMcpServer',
- previousAutoConfigureMcpServerConfig,
- currentAutoConfigureMcpServerConfig,
- );
- }
-
- if (currentSecureAtInceptionExecutionFrequencyConfig !== previousSecureAtInceptionExecutionFrequencyConfig) {
- await extension.context.updateGlobalStateValue(
- MEMENTO_SECURE_AT_INCEPTION_EXECUTION_FREQUENCY,
- currentSecureAtInceptionExecutionFrequencyConfig,
- );
-
- sendConfigChangedAnalytics(
- extension,
- logger,
- user,
- 'secureAtInceptionExecutionFrequency',
- previousSecureAtInceptionExecutionFrequencyConfig,
- currentSecureAtInceptionExecutionFrequencyConfig,
- );
- }
-
- await configureMcpHosts(vscodeContext, configuration);
-}
-
-function sendConfigChangedAnalytics(
- extension: IExtension,
- logger: ILog,
- user: User,
- field: string,
- oldValue: boolean | string,
- newValue: boolean | string,
-): void {
- const analyticsSender = AnalyticsSender.getInstance(logger, configuration, vsCodeCommands, extension.contextService);
-
- const event = new AnalyticsEvent(user.anonymousId, 'Config changed', []);
- event.getExtension().set(`config::${field}::oldValue`, oldValue);
- event.getExtension().set(`config::${field}::newValue`, newValue);
-
- analyticsSender.logEvent(event, () => {
- logger.info(`Analytics event sent for config change: securityAtInception.${field}`);
- });
-}
diff --git a/src/snyk/common/constants/languageServer.ts b/src/snyk/common/constants/languageServer.ts
index cfeb976aa..303de5df2 100644
--- a/src/snyk/common/constants/languageServer.ts
+++ b/src/snyk/common/constants/languageServer.ts
@@ -14,3 +14,4 @@ export const SNYK_SCAN = '$/snyk.scan';
export const SNYK_FOLDERCONFIG = '$/snyk.folderConfigs';
export const SNYK_SCANSUMMARY = '$/snyk.scanSummary';
export const SNYK_MCPSERVERURL = '$/snyk.mcpServerURL';
+export const SNYK_CONFIGURE_MCP = '$/snyk.configureSnykMCP';
diff --git a/src/snyk/common/languageServer/languageServer.ts b/src/snyk/common/languageServer/languageServer.ts
index 05a63ae14..1e354c9f7 100644
--- a/src/snyk/common/languageServer/languageServer.ts
+++ b/src/snyk/common/languageServer/languageServer.ts
@@ -1,9 +1,11 @@
import _ from 'lodash';
import { firstValueFrom, ReplaySubject, Subject } from 'rxjs';
+import * as vscode from 'vscode';
import { IAuthenticationService } from '../../base/services/authenticationService';
import { FolderConfig, IConfiguration } from '../configuration/configuration';
import {
SNYK_ADD_TRUSTED_FOLDERS,
+ SNYK_CONFIGURE_MCP,
SNYK_FOLDERCONFIG,
SNYK_HAS_AUTHENTICATED,
SNYK_LANGUAGE_SERVER_NAME,
@@ -51,6 +53,7 @@ export class LanguageServer implements ILanguageServer {
public static ReceivedFolderConfigsFromLs = false;
constructor(
+ private vscodeContext: vscode.ExtensionContext,
private user: User,
private configuration: IConfiguration,
private languageClientAdapter: ILanguageClientAdapter,
@@ -217,6 +220,35 @@ export class LanguageServer implements ILanguageServer {
client.onNotification(SNYK_SCANSUMMARY, ({ scanSummary }: { scanSummary: string }) => {
this.summaryProvider.updateSummaryPanel(scanSummary);
});
+
+ client.onNotification(
+ SNYK_CONFIGURE_MCP,
+ async (params: { command: string; args: string[]; env: Record; ideName: string }) => {
+ this.logger.info(`Received MCP configuration for VS Code Copilot`);
+ try {
+ // This notification is ONLY sent for VS Code (not Cursor/Windsurf)
+ // LS handles file writes for Cursor/Windsurf directly
+ this.vscodeContext.subscriptions.push(
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
+ // @ts-expect-error backward compatibility for older VS Code versions
+ vscode.lm.registerMcpServerDefinitionProvider('snyk-security-scanner', {
+ onDidChangeMcpServerDefinitions: new vscode.EventEmitter().event,
+ provideMcpServerDefinitions: async () => {
+ // @ts-expect-error backward compatibility for older VS Code versions
+ return [new vscode.McpStdioServerDefinition('Snyk', params.command, params.args, params.env)];
+ },
+ }),
+ );
+ this.logger.info('VS Code Copilot MCP server registered');
+ } catch (error) {
+ this.logger.debug(
+ `VS Code MCP Server Definition Provider API is not available. This feature requires VS Code version > 1.101.0.`,
+ );
+ }
+ },
+ );
}
// Initialization options are not semantically equal to server settings, thus separated here
diff --git a/src/snyk/common/watchers/configurationWatcher.ts b/src/snyk/common/watchers/configurationWatcher.ts
index 2322dd738..611cc0ca8 100644
--- a/src/snyk/common/watchers/configurationWatcher.ts
+++ b/src/snyk/common/watchers/configurationWatcher.ts
@@ -28,15 +28,9 @@ import { errorsLogs } from '../messages/errors';
import SecretStorageAdapter from '../vscode/secretStorage';
import { IWatcher } from './interfaces';
import { SNYK_CONTEXT } from '../constants/views';
-import { handleSecurityAtInceptionChange } from '../configuration/securityAtInceptionHandler';
-import { User } from '../user';
class ConfigurationWatcher implements IWatcher {
- constructor(
- private readonly logger: ILog,
- private readonly user: User,
- private readonly vscodeContext: vscode.ExtensionContext,
- ) {}
+ constructor(private readonly logger: ILog) {}
private async onChangeConfiguration(extension: IExtension, key: string): Promise {
if (key === ADVANCED_ORGANIZATION) {
@@ -71,8 +65,6 @@ class ConfigurationWatcher implements IWatcher {
} else if (key === TRUSTED_FOLDERS) {
extension.workspaceTrust.resetTrustedFoldersCache();
extension.viewManagerService.refreshAllViews();
- } else if (key === AUTO_CONFIGURE_MCP_SERVER || key === SECURITY_AT_INCEPTION_EXECUTION_FREQUENCY) {
- return handleSecurityAtInceptionChange(extension, this.logger, this.user, this.vscodeContext);
}
// from here on only for OSS and trusted folders
diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts
index c54c7d16d..525c5e7e5 100644
--- a/src/snyk/extension.ts
+++ b/src/snyk/extension.ts
@@ -91,7 +91,6 @@ import { SummaryProviderService } from './base/summary/summaryProviderService';
import { ProductTreeViewService } from './common/services/productTreeViewService';
import { Extension } from './common/vscode/extension';
import { MarkdownStringAdapter } from './common/vscode/markdownString';
-import { configureMcpHosts } from './cli/mcp/mcp';
class SnykExtension extends SnykLib implements IExtension {
public async activate(vscodeContext: vscode.ExtensionContext): Promise {
@@ -176,7 +175,7 @@ class SnykExtension extends SnykLib implements IExtension {
SecretStorageAdapter.init(vscodeContext);
configuration.setExtensionId(vscodeContext.extension.id);
- this.configurationWatcher = new ConfigurationWatcher(Logger, this.user, vscodeContext);
+ this.configurationWatcher = new ConfigurationWatcher(Logger);
this.notificationService = new NotificationService(vsCodeWindow, vsCodeCommands, configuration, Logger);
this.statusBarItem.show();
@@ -210,6 +209,7 @@ class SnykExtension extends SnykLib implements IExtension {
this.experimentService = new ExperimentService(this.user, Logger, configuration, snykConfiguration);
this.languageServer = new LanguageServer(
+ vscodeContext,
this.user,
configuration,
languageClientAdapter,
@@ -341,7 +341,6 @@ class SnykExtension extends SnykLib implements IExtension {
this.languageServer,
LsScanProduct.Code,
);
- await configureMcpHosts(vscodeContext, configuration);
vscodeContext.subscriptions.push(
vscode.window.registerTreeDataProvider(securityCodeView, codeSecurityIssueProvider),
codeSecurityTree,