diff --git a/mcp-servers/mcp-server-vscode/.vscode/settings.json b/mcp-servers/mcp-server-vscode/.vscode/settings.json index 28aaefd2..ef81f53c 100644 --- a/mcp-servers/mcp-server-vscode/.vscode/settings.json +++ b/mcp-servers/mcp-server-vscode/.vscode/settings.json @@ -1,15 +1,66 @@ // Place your settings in this file to overwrite default and user settings. { + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off", + "editor.bracketPairColorization.enabled": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + }, + "editor.guides.bracketPairs": "active", + "editor.formatOnPaste": true, + "editor.formatOnType": true, + "editor.formatOnSave": true, + "files.eol": "\n", "files.exclude": { "out": false, // set this to true to hide the "out" folder with the compiled JS files "dist": false // set this to true to hide the "dist" folder with the compiled JS files }, + "files.trimTrailingWhitespace": true, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "python.analysis.autoFormatStrings": true, + "python.analysis.autoImportCompletions": true, + "python.analysis.diagnosticMode": "workspace", + "python.analysis.fixAll": ["source.unusedImports"], + "python.analysis.inlayHints.functionReturnTypes": true, + "python.analysis.typeCheckingMode": "standard", + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.unusedImports": "explicit", + "source.organizeImports": "explicit", + "source.formatDocument": "explicit" + } + }, + "ruff.nativeServer": "on", "search.exclude": { "out": true, // set this to false to include "out" folder in search results "dist": true // set this to false to include "dist" folder in search results }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off", + // For use with optional extension: "streetsidesoftware.code-spell-checker" "cSpell.ignorePaths": ["ASSISTANT_BOOTSTRAP.md"], - "cSpell.words": ["modelcontextprotocol", "nosources", "vscodeignore"] + "cSpell.words": [ + "amodio", + "charliermarsh", + "dbaeumer", + "esbenp", + "fastmcp", + "modelcontextprotocol", + "nosources", + "pipx", + "toplevel", + "venv", + "vscodeignore", + "yarnrc" + ] } diff --git a/mcp-servers/mcp-server-vscode/src/ASSISTANT_BOOTSTRAP.md b/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md similarity index 98% rename from mcp-servers/mcp-server-vscode/src/ASSISTANT_BOOTSTRAP.md rename to mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md index a0bbcba4..5f188175 100644 --- a/mcp-servers/mcp-server-vscode/src/ASSISTANT_BOOTSTRAP.md +++ b/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md @@ -44,12 +44,12 @@ Your task is to methodically gather context from all relevant parts of the proje ### Step 1: Load MCP Context Documentation -- **Action:** +- **Action:** Use your directory listing tool to list all files in: `/workspaces/semanticworkbench/mcp-servers/ai-assist-content` -- **Then:** +- **Then:** Read the following files: - `README.md` - `mcp-llms-full.txt` @@ -61,12 +61,12 @@ These files provide the conceptual framework for MCP and contextual guidelines. ### Step 2: Examine the Reference Implementation -- **Action:** +- **Action:** List all files in the MCP Server Giphy project: `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy` -- **Then:** +- **Then:** Specifically review the files in its `server` subdirectory (e.g., `main.py`, `giphy_search.py`) and high-level configuration files (like `pyproject.toml` and `.vscode` settings). This will help you understand best practices and reference design patterns used in our MCP projects. @@ -75,12 +75,12 @@ This will help you understand best practices and reference design patterns used ### Step 3: Explore the VSCode MCP Server VSCode Extension Project -- **Action:** +- **Action:** List all files in: `/workspaces/semanticworkbench/mcp-servers/mcp-server-vscode` -- **Then:** +- **Then:** Recursively read the contents of key files and directories, including but not limited to: - `README.md` - `package.json` diff --git a/mcp-servers/mcp-server-vscode/README.md b/mcp-servers/mcp-server-vscode/README.md index ae161657..ce44d089 100644 --- a/mcp-servers/mcp-server-vscode/README.md +++ b/mcp-servers/mcp-server-vscode/README.md @@ -6,23 +6,23 @@ The **VSCode MCP Server** is a VSCode extension that acts as a Model Context Pro ## Features -- **Automatic Startup:** +- **Automatic Startup:** The extension activates automatically on VSCode startup (using `"activationEvents": ["*"]` in `package.json`), ensuring the MCP server is always running without manual intervention. -- **MCP Server Integration:** +- **MCP Server Integration:** Built using the MCP TypeScript SDK (`@modelcontextprotocol/sdk`), the extension instantiates an MCP server that registers diagnostic tools and handles MCP protocol messages. -- **Diagnostic Tool (`code_checker`):** +- **Diagnostic Tool (`code_checker`):** The registered `code_checker` tool collects diagnostics from VSCode’s built-in language services, filtering out files without errors. When invoked, it returns a formatted JSON object containing diagnostic information (only for files with issues). -- **SSE Communication:** +- **SSE Communication:** An Express-based HTTP server runs on port 6010, exposing: - A **GET `/sse`** endpoint to establish a long-lived Server-Sent Events (SSE) connection. - - A **POST `/messages`** endpoint for receiving MCP messages from external clients (such as your AI assistant). + - A **POST `/messages`** endpoint for receiving MCP messages from external clients (such as your AI assistant). Special care is taken to handle the request body properly—thanks to passing the already-parsed `req.body` to avoid stream-related errors. -- **Verbose Logging:** +- **Verbose Logging:** All activity, including server startup, SSE connection status, and message handling events, is logged to an output channel named **"VSCode MCP Server"** to aid debugging and transparency. ## Project Structure @@ -42,14 +42,14 @@ vscode-mcp-server/ ## Setup and Installation -1. **Install Dependencies:** +1. **Install Dependencies:** Ensure you have Node.js (v16 or higher) and pnpm installed. Then, from the project directory, run: ```bash pnpm install ``` -2. **Package the Extension:** +2. **Package the Extension:** To package the extension, execute: ```bash pnpm run package-extension @@ -115,10 +115,10 @@ To use the VSCode MCP Server with Claude Desktop, you need to configure Claude D ## Debugging the Extension -1. **Start Debugging:** +1. **Start Debugging:** Open the project in VSCode, then press **F5** to launch the Extension Development Host. This will automatically activate the extension based on the `"activationEvents": ["*"]` setting. -2. **MCP Server Operation:** +2. **MCP Server Operation:** On activation, the extension: - Starts the MCP server which registers the `code_checker` tool. - Sets up an Express HTTP server on port **6010** with: diff --git a/mcp-servers/mcp-server-vscode/mcp-server-vscode-0.0.1.vsix b/mcp-servers/mcp-server-vscode/mcp-server-vscode-0.0.1.vsix deleted file mode 100644 index ed82cf69..00000000 Binary files a/mcp-servers/mcp-server-vscode/mcp-server-vscode-0.0.1.vsix and /dev/null differ diff --git a/mcp-servers/mcp-server-vscode/src/extension.d.ts b/mcp-servers/mcp-server-vscode/src/extension.d.ts index ac0ddb93..08bd2584 100644 --- a/mcp-servers/mcp-server-vscode/src/extension.d.ts +++ b/mcp-servers/mcp-server-vscode/src/extension.d.ts @@ -1,3 +1,3 @@ -import * as vscode from 'vscode'; -export declare function activate(context: vscode.ExtensionContext): void; +import * as vscode from "vscode"; +export declare const activate: (context: vscode.ExtensionContext) => void; export declare function deactivate(): void; diff --git a/mcp-servers/mcp-server-vscode/src/extension.ts b/mcp-servers/mcp-server-vscode/src/extension.ts index f31aa11d..ecff4705 100644 --- a/mcp-servers/mcp-server-vscode/src/extension.ts +++ b/mcp-servers/mcp-server-vscode/src/extension.ts @@ -1,123 +1,136 @@ -import * as vscode from 'vscode'; -import dedent from 'dedent'; -import express from 'express'; -import * as http from 'http'; -import { Request, Response } from 'express'; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import dedent from "dedent"; +import express, { Request, Response } from "express"; +import * as http from "http"; +import * as vscode from "vscode"; +import { DiagnosticSeverity } from "vscode"; +import { z } from "zod"; +import { codeCheckerTool } from "./tools/code_checker"; const extensionName = "vscode-mcp-server"; const extensionDisplayName = "VSCode MCP Server"; -export function activate(context: vscode.ExtensionContext) { - // Create the output channel for logging - const outputChannel = vscode.window.createOutputChannel(extensionDisplayName); +export const activate = (context: vscode.ExtensionContext) => { + // Create the output channel for logging + const outputChannel = vscode.window.createOutputChannel(extensionDisplayName); - // Write an initial message to ensure the channel appears in the Output dropdown - outputChannel.appendLine(`Activating ${extensionDisplayName}...`); - // Uncomment to automatically switch to the output tab and this extension channel on activation - // outputChannel.show(); + // Write an initial message to ensure the channel appears in the Output dropdown + outputChannel.appendLine(`Activating ${extensionDisplayName}...`); + // Uncomment to automatically switch to the output tab and this extension channel on activation + // outputChannel.show(); - // Initialize the MCP server instance - const mcpServer = new McpServer({ - name: extensionName, - version: "0.0.1", - }); + // Initialize the MCP server instance + const mcpServer = new McpServer({ + name: extensionName, + version: "0.0.1", + }); - // Register the "code_checker" tool. - // This tool retrieves diagnostics from VSCode's language services, - // filtering out files without issues. - mcpServer.tool( - "code_checker", - dedent` - Retrieve diagnostics from VSCode's language services for the active workspace. - Use this tool after making changes to any code the filesystem to ensure no new - errors were introduced, or when requested by the user. - `.trim(), - {}, - async () => { - // Retrieve diagnostics from all files - const diagnosticsByFile = vscode.languages.getDiagnostics(); - // Filter to only include files that have diagnostics - const aggregated = diagnosticsByFile - .filter(([_uri, diags]) => diags.filter(diag => diag.severity !== vscode.DiagnosticSeverity.Hint).length > 0) - .map(([uri, diags]) => ({ - file: uri.fsPath, - diagnostics: diags.filter(diag => diag.severity !== vscode.DiagnosticSeverity.Hint).map(diag => ({ - severity: vscode.DiagnosticSeverity[diag.severity], - message: diag.message, - source: diag.source || "" - })) - })); + // Register the "code_checker" tool. + // This tool retrieves diagnostics from VSCode's language services, + // filtering out files without issues. + mcpServer.tool( + "code_checker", + dedent` + Retrieve diagnostics from VSCode's language services for the active workspace. + Use this tool after making changes to any code in the filesystem to ensure no new + errors were introduced, or when requested by the user. + `.trim(), + // Passing the raw shape object directly + { + severityLevel: z + .enum(["Error", "Warning", "Information", "Hint"]) + .default("Warning") + .describe( + "Minimum severity level for checking issues: 'Error', 'Warning', 'Information', or 'Hint'." + ), + }, + async (params: { + severityLevel?: "Error" | "Warning" | "Information" | "Hint"; + }) => { + const severityLevel = params.severityLevel + ? DiagnosticSeverity[params.severityLevel] + : DiagnosticSeverity.Warning; + const result = await codeCheckerTool(severityLevel); + return { + ...result, + content: result.content.map((c) => ({ + ...c, + text: typeof c.text === "string" ? c.text : String(c.text), + type: "text", + })), + }; + } + ); - if (aggregated.length === 0) { - // If no diagnostics found, return an empty result - return { content: [{ type: "text", text: "No issues found." }] }; - } + // Set up an Express app to handle SSE connections + const app = express(); + const port = 6010; + let sseTransport: SSEServerTransport | undefined; - // Otherwise, return the aggregated diagnostics as formatted JSON - return { content: [{ type: "text", text: JSON.stringify(aggregated, null, 2) }] }; - } - ); - - // Set up an Express app to handle SSE connections - const app = express(); - const port = 6010; - let sseTransport: SSEServerTransport | undefined; + // GET /sse endpoint: the external MCP client connects here (SSE) + app.get("/sse", async (_req: Request, res: Response) => { + outputChannel.appendLine("SSE connection initiated..."); + sseTransport = new SSEServerTransport("/messages", res); + try { + await mcpServer.connect(sseTransport); + outputChannel.appendLine("MCP Server connected via SSE."); + outputChannel.appendLine( + `SSE Transport sessionId: ${sseTransport.sessionId}` + ); + } catch (err) { + outputChannel.appendLine("Error connecting MCP Server via SSE: " + err); + } + }); - // GET /sse endpoint: the external MCP client connects here (SSE) - app.get('/sse', async (_req: Request, res: Response) => { - outputChannel.appendLine("SSE connection initiated..."); - sseTransport = new SSEServerTransport("/messages", res); - try { - await mcpServer.connect(sseTransport); - outputChannel.appendLine("MCP Server connected via SSE."); - outputChannel.appendLine(`SSE Transport sessionId: ${sseTransport.sessionId}`); - } catch (err) { - outputChannel.appendLine("Error connecting MCP Server via SSE: " + err); - } - }); + // POST /messages endpoint: the external MCP client sends messages here + app.post("/messages", express.json(), async (req: Request, res: Response) => { + // Log in output channel + outputChannel.appendLine( + `POST /messages: Payload - ${JSON.stringify(req.body, null, 2)}` + ); - // POST /messages endpoint: the external MCP client sends messages here - app.post('/messages', express.json(), async (req: Request, res: Response) => { - // Log in output channel - outputChannel.appendLine(`POST /messages: Payload - ${JSON.stringify(req.body, null, 2)}`); - - if (sseTransport) { - // Log the session ID of the transport to confirm its initialization - outputChannel.appendLine(`SSE Transport sessionId: ${sseTransport.sessionId}`); - try { - // Note: Passing req.body to handlePostMessage is critical because express.json() - // consumes the request stream. Without this, attempting to re-read the stream - // within handlePostMessage would result in a "stream is not readable" error. - await sseTransport.handlePostMessage(req, res, req.body); - outputChannel.appendLine("Handled POST /messages successfully."); - } catch (err) { - outputChannel.appendLine("Error handling POST /messages: " + err); - } - } else { - res.status(500).send("SSE Transport not initialized."); - outputChannel.appendLine("POST /messages failed: SSE Transport not initialized."); - } - }); + if (sseTransport) { + // Log the session ID of the transport to confirm its initialization + outputChannel.appendLine( + `SSE Transport sessionId: ${sseTransport.sessionId}` + ); + try { + // Note: Passing req.body to handlePostMessage is critical because express.json() + // consumes the request stream. Without this, attempting to re-read the stream + // within handlePostMessage would result in a "stream is not readable" error. + await sseTransport.handlePostMessage(req, res, req.body); + outputChannel.appendLine("Handled POST /messages successfully."); + } catch (err) { + outputChannel.appendLine("Error handling POST /messages: " + err); + } + } else { + res.status(500).send("SSE Transport not initialized."); + outputChannel.appendLine( + "POST /messages failed: SSE Transport not initialized." + ); + } + }); - // Create and start the HTTP server - const server = http.createServer(app); - server.listen(port, () => { - outputChannel.appendLine(`MCP SSE Server running at http://127.0.0.1:${port}/sse`); - }); + // Create and start the HTTP server + const server = http.createServer(app); + server.listen(port, () => { + outputChannel.appendLine( + `MCP SSE Server running at http://127.0.0.1:${port}/sse` + ); + }); - // Add disposal to shut down the HTTP server and output channel on extension deactivation - context.subscriptions.push({ - dispose: () => { - server.close(); - outputChannel.dispose(); - } - }); + // Add disposal to shut down the HTTP server and output channel on extension deactivation + context.subscriptions.push({ + dispose: () => { + server.close(); + outputChannel.dispose(); + }, + }); - outputChannel.appendLine(`${extensionDisplayName} activated.`); + outputChannel.appendLine(`${extensionDisplayName} activated.`); } export function deactivate() { - // Clean-up is managed by the disposables added in the activate method. + // Clean-up is managed by the disposables added in the activate method. } diff --git a/mcp-servers/mcp-server-vscode/src/tools/code_checker.d.ts b/mcp-servers/mcp-server-vscode/src/tools/code_checker.d.ts new file mode 100644 index 00000000..3f244262 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/code_checker.d.ts @@ -0,0 +1,8 @@ +import { DiagnosticSeverity } from 'vscode'; +export declare const codeCheckerTool: (severityLevel?: DiagnosticSeverity) => { + content: { + type: string; + text: string; + }[]; + isError: boolean; +}; diff --git a/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts b/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts new file mode 100644 index 00000000..513d0728 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts @@ -0,0 +1,33 @@ +import { DiagnosticSeverity, languages } from 'vscode'; + +/** + * Retrieve diagnostics for the active workspace, with filtering by severity level. + * + * @param 'Warning' - Minimum severity level to include (default is Warning). + */ +export const codeCheckerTool = ( + severityLevel: DiagnosticSeverity = DiagnosticSeverity.Warning +) => { + // Retrieve diagnostics from all files + const diagnosticsByFile = languages.getDiagnostics(); + + // Filter diagnostics based on the target severity + const aggregated = diagnosticsByFile + .filter(([_uri, diags]) => diags.some(diag => diag.severity <= severityLevel)) + .map(([uri, diags]) => ({ + file: uri.fsPath, + diagnostics: diags.filter(diag => diag.severity <= severityLevel).map(diag => ({ + severity: DiagnosticSeverity[diag.severity], + message: diag.message, + source: diag.source || "" + })) + })); + + if (aggregated.length === 0) { + // If no diagnostics found, return an empty result + return { content: [{ type: "text", text: "No issues found." }], isError: false }; + } + + // Otherwise, return the aggregated diagnostics as formatted JSON + return { content: [{ type: "text", text: JSON.stringify(aggregated, null, 2) }], isError: false }; +}