diff --git a/assistants/codespace-assistant/.vscode/extensions.json b/assistants/codespace-assistant/.vscode/extensions.json new file mode 100644 index 00000000..15d1356a --- /dev/null +++ b/assistants/codespace-assistant/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["semanticworkbenchteam.mcp-server-vscode"] +} diff --git a/assistants/codespace-assistant/.vscode/settings.json b/assistants/codespace-assistant/.vscode/settings.json index c990f36c..f20e15f6 100644 --- a/assistants/codespace-assistant/.vscode/settings.json +++ b/assistants/codespace-assistant/.vscode/settings.json @@ -84,6 +84,7 @@ "pyright", "pytest", "semanticworkbench", + "semanticworkbenchteam", "tiktoken", "updown", "virtualenvs", diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/config.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/config.py index a2e24fd5..1b4d34f0 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/config.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/config.py @@ -1,19 +1,23 @@ import os + from pydantic_settings import BaseSettings data_folder = os.environ.get("DATA_FOLDER", ".data") log_level = os.environ.get("LOG_LEVEL", "INFO") + def load_required_env_var(env_var_name: str) -> str: value = os.environ.get(env_var_name, "") if not value: raise ValueError(f"Missing required environment variable: {env_var_name}") return value + huggingface_token = load_required_env_var("HUGGINGFACE_TOKEN") openai_api_key = load_required_env_var("OPENAI_API_KEY") serpapi_api_key = load_required_env_var("SERPAPI_API_KEY") + class Settings(BaseSettings): data_folder: str = data_folder log_level: str = log_level diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/mdconvert.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/mdconvert.py index b3824c6d..e3fd345c 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/mdconvert.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/mdconvert.py @@ -36,6 +36,7 @@ # FIXME: Pass the extract_dir into the MarkdownConverter instead of importing the settings from ... import settings + class _CustomMarkdownify(markdownify.MarkdownConverter): """ A custom version of markdownify's MarkdownConverter. Changes include: diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_inspector_tool.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_inspector_tool.py index add3b830..8efff63f 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_inspector_tool.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_inspector_tool.py @@ -74,7 +74,7 @@ def forward_initial_exam_mode(self, file_path, question) -> str | None: ] return self.model(messages).content - def forward(self, file_path, question: Optional[str] = None) -> str: # type: ignore + def forward(self, file_path, question: Optional[str] = None) -> str: # type: ignore result = self.md_converter.convert(file_path) if file_path[-4:] in [".png", ".jpg"]: diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_web_browser.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_web_browser.py index 0dd7b48f..74e5515f 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_web_browser.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/text_web_browser.py @@ -12,7 +12,6 @@ import pathvalidate import requests from serpapi import GoogleSearch - from smolagents import Tool from .cookies import COOKIES @@ -253,9 +252,8 @@ def _prev_visit(url): redacted_version = redacted_version.replace("Your browser can't play this video.", "") web_snippets.append(redacted_version) - content = ( - f"A Google search for '{query}' found {len(web_snippets)} results:\n\n## Web Results\n" - + "\n\n".join(web_snippets) + content = f"A Google search for '{query}' found {len(web_snippets)} results:\n\n## Web Results\n" + "\n\n".join( + web_snippets ) self._set_page_content(content) @@ -329,7 +327,7 @@ def _fetch_page(self, url: str) -> None: self.page_title = "Error 404" self._set_page_content(f"## Error 404\n\nFile not found: {download_path}") except requests.exceptions.RequestException as request_exception: - response_obj = getattr(request_exception, 'response', None) + response_obj = getattr(request_exception, "response", None) if response_obj: self.page_title = f"Error {response_obj.status_code}" content_type = response_obj.headers.get("content-type", "") @@ -432,7 +430,6 @@ def forward(self, url: str) -> Any: with open(new_path, "wb") as f: f.write(response.content) - return f"File was downloaded and saved under path {new_path}." @@ -495,9 +492,7 @@ def forward(self) -> Any: class PageDownTool(Tool): name = "page_down" - description = ( - "Scroll the viewport DOWN one page-length in the current webpage and return the new viewport content." - ) + description = "Scroll the viewport DOWN one page-length in the current webpage and return the new viewport content." inputs = {} output_type = "string" diff --git a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/visual_qa.py b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/visual_qa.py index b506d39a..eb5accfa 100644 --- a/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/visual_qa.py +++ b/mcp-servers/mcp-server-open-deep-research/mcp_server/libs/open_deep_research/visual_qa.py @@ -10,9 +10,8 @@ from dotenv import load_dotenv from huggingface_hub import InferenceClient from PIL import Image -from transformers import AutoProcessor - from smolagents import Tool, tool +from transformers import AutoProcessor # FIXME: Find a way to pass the settings object in, instead of importing it directly from ... import settings @@ -125,7 +124,7 @@ class VisualQATool(Tool): client = InferenceClient("HuggingFaceM4/idefics2-8b-chatty") - def forward(self, image_path: str, question: Optional[str] = None) -> str: # type: ignore + def forward(self, image_path: str, question: Optional[str] = None) -> str: # type: ignore output = "" add_note = False if not question: @@ -140,9 +139,7 @@ def forward(self, image_path: str, question: Optional[str] = None) -> str: # typ output = process_images_and_text(new_image_path, question, self.client) if add_note: - output = ( - f"You did not provide a particular question, so here is a detailed caption for the image: {output}" - ) + output = f"You did not provide a particular question, so here is a detailed caption for the image: {output}" return output diff --git a/mcp-servers/mcp-server-vscode/.eslintrc.cjs b/mcp-servers/mcp-server-vscode/.eslintrc.cjs new file mode 100644 index 00000000..cb164ac1 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/.eslintrc.cjs @@ -0,0 +1,51 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: ['plugin:react/recommended', 'react-app'], + ignorePatterns: ['build', '.*.js', '*.config.js', 'node_modules'], + overrides: [], + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['react', '@typescript-eslint', 'import', 'react-hooks', 'react-security'], + rules: { + '@typescript-eslint/brace-style': ['off'], + '@typescript-eslint/space-before-function-paren': [ + 'error', + { anonymous: 'always', named: 'never', asyncArrow: 'always' }, + ], + '@typescript-eslint/semi': ['error', 'always'], + '@typescript-eslint/triple-slash-reference': ['error', { types: 'prefer-import' }], + '@typescript-eslint/indent': ['off'], + '@typescript-eslint/comma-dangle': ['error', 'always-multiline'], + '@typescript-eslint/strict-boolean-expressions': 'off', + '@typescript-eslint/member-delimiter-style': [ + 'error', + { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, + ], + '@typescript-eslint/explicit-function-return-type': 'off', + 'react/jsx-props-no-spreading': 'warn', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'react/react-in-jsx-scope': 'off', + }, + settings: { + react: { + version: 'detect', + }, + }, +}; diff --git a/mcp-servers/mcp-server-vscode/.vscode-test.mjs b/mcp-servers/mcp-server-vscode/.vscode-test.mjs index b62ba25f..db34f3eb 100644 --- a/mcp-servers/mcp-server-vscode/.vscode-test.mjs +++ b/mcp-servers/mcp-server-vscode/.vscode-test.mjs @@ -1,5 +1,5 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: 'out/test/**/*.test.js', }); diff --git a/mcp-servers/mcp-server-vscode/.vscode/settings.json b/mcp-servers/mcp-server-vscode/.vscode/settings.json index 2c4e60ce..03498ed3 100644 --- a/mcp-servers/mcp-server-vscode/.vscode/settings.json +++ b/mcp-servers/mcp-server-vscode/.vscode/settings.json @@ -1,48 +1,134 @@ // 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]": { + // 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.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "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 - }, - // For use with optional extension: "streetsidesoftware.code-spell-checker" - "cSpell.ignorePaths": ["ASSISTANT_BOOTSTRAP.md"], - "cSpell.words": [ - "amodio", - "charliermarsh", - "dbaeumer", - "esbenp", - "fastmcp", - "modelcontextprotocol", - "nosources", - "pipx", - "toplevel", - "venv", - "vscodeignore", - "yarnrc" - ] + "editor.formatOnPaste": true, + "editor.formatOnType": true, + "editor.formatOnSave": true, + "eslint.enable": true, + "eslint.options": { + "overrideConfigFile": ".eslintrc.cjs" + }, + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "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 + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + } + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + } + }, + "search.exclude": { + "**/node_modules": true, + "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 + }, + "typescript.updateImportsOnFileMove.enabled": "always", + "better-comments.highlightPlainText": true, + "better-comments.multilineComments": true, + "better-comments.tags": [ + { + "tag": "!", + "color": "#FF2D00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "?", + "color": "#3498DB", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "//", + "color": "#474747", + "strikethrough": true, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "todo", + "color": "#FF8C00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "fixme", + "color": "#FF2D00", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + }, + { + "tag": "*", + "color": "#98C379", + "strikethrough": false, + "underline": false, + "backgroundColor": "transparent", + "bold": false, + "italic": false + } + ], + // For use with optional extension: "streetsidesoftware.code-spell-checker" + "cSpell.ignorePaths": ["ASSISTANT_BOOTSTRAP.md"], + "cSpell.words": [ + "amodio", + "charliermarsh", + "dbaeumer", + "esbenp", + "fastmcp", + "modelcontextprotocol", + "nosources", + "pipx", + "semanticworkbench", + "toplevel", + "venv", + "vscodeignore", + "yarnrc" + ] } diff --git a/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md b/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md index 5f188175..d28e4a36 100644 --- a/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md +++ b/mcp-servers/mcp-server-vscode/ASSISTANT_BOOTSTRAP.md @@ -6,35 +6,35 @@ Welcome to the VSCode MCP Server project! This VSCode extension serves as an MCP **Instructions:** -- Read this document in its entirety. -- Follow the step-by-step operations described in the “Onboarding Steps” section. -- Use your integrated file system tools (or available commands like `list_directory` and `read_file`) to explore directories and load file content. -- Once you have gathered all context, summarize your understanding and propose next steps based on the project’s current state and future plans. +- Read this document in its entirety. +- Follow the step-by-step operations described in the “Onboarding Steps” section. +- Use your integrated file system tools (or available commands like `list_directory` and `read_file`) to explore directories and load file content. +- Once you have gathered all context, summarize your understanding and propose next steps based on the project’s current state and future plans. ## 2. Project Overview The VSCode MCP Server is designed to: -- **Automatically Start:** Activate on VSCode startup (using `"activationEvents": ["*"]` in package.json) so that the MCP server runs without manual intervention. -- **Expose an MCP Server:** Instantiate an MCP server using the TypeScript SDK (`@modelcontextprotocol/sdk`) within the extension. -- **Provide Diagnostic Tools:** Register a `code_checker` tool that retrieves diagnostics (errors/warnings) from VSCode's built-in language services, filtering out files without issues. -- **Communicate via SSE:** Run an Express-based HTTP server that listens on port 6010, providing a GET `/sse` endpoint (to establish a long-lived SSE connection) and a POST `/messages` endpoint for incoming MCP messages. -- **Log Activity:** All activity, including server startup, SSE connection status, and message handling events, is logged to an output channel named **"MCP Server Logs"**—this aids in debugging and ensures transparency of operations. +- **Automatically Start:** Activate on VSCode startup (using `"activationEvents": ["*"]` in package.json) so that the MCP server runs without manual intervention. +- **Expose an MCP Server:** Instantiate an MCP server using the TypeScript SDK (`@modelcontextprotocol/sdk`) within the extension. +- **Provide Diagnostic Tools:** Register a `code_checker` tool that retrieves diagnostics (errors/warnings) from VSCode's built-in language services, filtering out files without issues. +- **Communicate via SSE:** Run an Express-based HTTP server that listens on port 6010, providing a GET `/sse` endpoint (to establish a long-lived SSE connection) and a POST `/messages` endpoint for incoming MCP messages. +- **Log Activity:** All activity, including server startup, SSE connection status, and message handling events, is logged to an output channel named **"MCP Server Logs"**—this aids in debugging and ensures transparency of operations. --- ### 2.2 The MCP Ecosystem -- **MCP (Model Context Protocol):** - A protocol designed to connect language models with external tools and data sources. Familiarize yourself with the MCP context by reviewing our documentation files located in the `ai-assist-content` directory. +- **MCP (Model Context Protocol):** + A protocol designed to connect language models with external tools and data sources. Familiarize yourself with the MCP context by reviewing our documentation files located in the `ai-assist-content` directory. ### 2.3 Related Projects for Reference -- **MCP Context Documentation:** - Located at `/workspaces/semanticworkbench/mcp-servers/ai-assist-content` (files such as `README.md`, `mcp-llms-full.txt`, and `mcp-python-sdk-README.md`). These files explain the MCP protocol and provide guidelines for building MCP servers. +- **MCP Context Documentation:** + Located at `/workspaces/semanticworkbench/mcp-servers/ai-assist-content` (files such as `README.md`, `mcp-llms-full.txt`, and `mcp-python-sdk-README.md`). These files explain the MCP protocol and provide guidelines for building MCP servers. -- **Reference Implementation – MCP Server Giphy:** - Located at `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy`. This project serves as a reference for our code patterns and configurations in the MCP server ecosystem. +- **Reference Implementation – MCP Server Giphy:** + Located at `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy`. This project serves as a reference for our code patterns and configurations in the MCP server ecosystem. --- @@ -44,16 +44,16 @@ Your task is to methodically gather context from all relevant parts of the proje ### Step 1: Load MCP Context Documentation -- **Action:** - Use your directory listing tool to list all files in: +- **Action:** + Use your directory listing tool to list all files in: - `/workspaces/semanticworkbench/mcp-servers/ai-assist-content` + `/workspaces/semanticworkbench/mcp-servers/ai-assist-content` -- **Then:** - Read the following files: - - `README.md` - - `mcp-llms-full.txt` - - `mcp-python-sdk-README.md` +- **Then:** + Read the following files: + - `README.md` + - `mcp-llms-full.txt` + - `mcp-python-sdk-README.md` These files provide the conceptual framework for MCP and contextual guidelines. @@ -61,13 +61,13 @@ These files provide the conceptual framework for MCP and contextual guidelines. ### Step 2: Examine the Reference Implementation -- **Action:** - List all files in the MCP Server Giphy project: +- **Action:** + List all files in the MCP Server Giphy project: - `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy` + `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy` -- **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). +- **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,17 +75,17 @@ This will help you understand best practices and reference design patterns used ### Step 3: Explore the VSCode MCP Server VSCode Extension Project -- **Action:** - List all files in: +- **Action:** + List all files in: - `/workspaces/semanticworkbench/mcp-servers/mcp-server-vscode` + `/workspaces/semanticworkbench/mcp-servers/mcp-server-vscode` -- **Then:** - Recursively read the contents of key files and directories, including but not limited to: - - `README.md` - - `package.json` - - `.vscode` (and all its contents) - - `src/extension.ts` +- **Then:** + Recursively read the contents of key files and directories, including but not limited to: + - `README.md` + - `package.json` + - `.vscode` (and all its contents) + - `src/extension.ts` Ensure you understand the project structure and how the tool interfaces work. @@ -96,15 +96,15 @@ Ensure you understand the project structure and how the tool interfaces work. Once you have assimilated the project context: 1. **Immediate Tasks:** - - Ask user to have you test the tool and verify it works as expected. - - Assess the aggregated diagnostic outputs and propose improvements for output formatting. - - Add support at both the server config args and the client side for filtering diagnostics by severity (e.g., only show errors, only show warnings, ignore "hint" diagnostics). + - Ask user to have you test the tool and verify it works as expected. + - Assess the aggregated diagnostic outputs and propose improvements for output formatting. + - Add support at both the server config args and the client side for filtering diagnostics by severity (e.g., only show errors, only show warnings, ignore "hint" diagnostics). 2. **Future Work:** - - Consider what other capabilities should be added to the `code_checker` tool or the MCP server itself. + - Consider what other capabilities should be added to the `code_checker` tool or the MCP server itself. 3. **Reporting:** - - Summarize your complete understanding of the project and generate a detailed plan for subsequent development tasks. + - Summarize your complete understanding of the project and generate a detailed plan for subsequent development tasks. --- @@ -112,9 +112,9 @@ Once you have assimilated the project context: 1. **Read this document completely and follow the instructions it contains.** 2. **Execute the onboarding steps in the order provided:** - - Start by listing and reading the MCP context files in `/workspaces/semanticworkbench/mcp-servers/ai-assist-content`. - - Next, review the reference implementation in `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy`. - - Then, recursively read key files in the VSCode MCP Server project. + - Start by listing and reading the MCP context files in `/workspaces/semanticworkbench/mcp-servers/ai-assist-content`. + - Next, review the reference implementation in `/workspaces/semanticworkbench/mcp-servers/mcp-server-giphy`. + - Then, recursively read key files in the VSCode MCP Server project. 3. **Document your findings and propose next steps** for further development. 4. **Finally, report back your complete understanding** and a detailed plan for subsequent enhancements. diff --git a/mcp-servers/mcp-server-vscode/README.md b/mcp-servers/mcp-server-vscode/README.md index ce44d089..dd3d3540 100644 --- a/mcp-servers/mcp-server-vscode/README.md +++ b/mcp-servers/mcp-server-vscode/README.md @@ -6,141 +6,192 @@ The **VSCode MCP Server** is a VSCode extension that acts as a Model Context Pro ## Features -- **Automatic Startup:** - The extension activates automatically on VSCode startup (using `"activationEvents": ["*"]` in `package.json`), ensuring the MCP server is always running without manual intervention. +- **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:** - Built using the MCP TypeScript SDK (`@modelcontextprotocol/sdk`), the extension instantiates an MCP server that registers diagnostic tools and handles MCP protocol messages. +- **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`):** - 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). +- **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:** - An Express-based HTTP server runs on port 6010, exposing: +- **Focus Editor Tool (`focus_editor`)**: Opens a specific file in the VSCode editor and navigates to a designated line and column. Useful for bringing files into visual focus for the user but does not include file content in the tool call result. +- **Search Symbol Tool (`search_symbol`)**: Searches for symbols in the workspace, using "Go to Definition" primarily, with a fallback to text search (similar to Ctrl+Shift+F). Can optionally open the results in the editor using the `focus_editor` tool. - - 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). - Special care is taken to handle the request body properly—thanks to passing the already-parsed `req.body` to avoid stream-related errors. +- **Debug Session Management Tools:** + The extension provides tools to manage VSCode debug sessions directly using MCP: -- **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. + - `list_debug_sessions`: Retrieve all active debug sessions in the workspace. + - `start_debug_session`: Start a new debug session with the provided configuration. + - `stop_debug_session`: Stop debug sessions matching a specific session name. + - `restart_debug_session`: Restart a debug session by stopping it and then starting it with the provided configuration (new!). -## Project Structure +- **SSE Communication:** + An Express-based HTTP server runs on a configurable port (default: 6010) and dynamically handles port conflicts. It exposes: -``` -vscode-mcp-server/ -├── .vscode/ # VSCode workspace and debugging configurations -├── node_modules/ # Installed dependencies -├── package.json # Project metadata and scripts -├── pnpm-lock.yaml # Dependency lock file -├── src/ -│ └── extension.ts # Main extension code setting up the MCP server, tools, and SSE endpoints -├── tsconfig.json # TypeScript configuration -├── webpack.config.js # Webpack configuration for bundling the extension -└── README.md # This file -``` + - A **GET `/sse`** endpoint to establish a long-lived Server-Sent Events (SSE) connection. If the default port (6010) is unavailable, users can configure a new one via their VSCode settings (see Dynamic Port Configuration below). + - 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. -## Setup and Installation +- **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. -1. **Install Dependencies:** - Ensure you have Node.js (v16 or higher) and pnpm installed. Then, from the project directory, run: +## Using the Extension from Claude Desktop (MCP Client) - ```bash - pnpm install - ``` +To use the VSCode MCP Server with Claude Desktop, you need to configure Claude Desktop to connect to the MCP server running in VSCode. Since the implementation of the MCP server uses SSE transport, and Claude Desktop only supports stdio transport, you need to use a [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) to bridge the communication between the two. -2. **Package the Extension:** - To package the extension, execute: - ```bash - pnpm run package-extension - ``` - This will generate a `.vsix` file in the project root. +1. **Install MCP Proxy:** -## Installing the Extension Locally + - Option 1: With uv (recommended) -1. **Open Your Main VSCode Instance:** + ``` + uv tool install mcp-proxy + ``` - Launch your main VSCode (outside the Extension Development Host). + - Option 2: With pipx (alternative) -2. **Install the VSIX Package:** + ``` + pipx install mcp-proxy + ``` - - Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) to open the Command Palette. - - Type and select "Extensions: Install from VSIX...". - - Navigate to and select the generated .vsix file. +2. **Configure Claude Desktop:** -3. **Reload and Verify:** + - Open Claude Desktop and navigate to the **File** > **Settings** > **Developer** tab. + - Click **Edit Config** to open the config file, launch your desired editor to modify the config file contents. + - Add a new entry to **mcpServers** with the following details: + + ```json + { + "mcpServers": { + "vscode": { + "command": "mcp-proxy", + "args": ["http://127.0.0.1:6010/sse"] + } + } + } + ``` + +3. **Restart Claude Desktop:** - After installation, reload VSCode (via "Developer: Reload Window" from the Command Palette) and verify that the extension is active. Check the "MCP Server Logs" output channel to see logs confirming that the MCP server has started and is listening on port 6010. + - You **must** restart Claude Desktop for the changes to take effect by using the **File** > **Exit** option. + - NOTE: This is different than just closing the window or using **File** > **Close**, which leaves the application running in the background. + - After existing and then starting again, Claude Desktop should now be able to connect to the MCP server running in VSCode. -## Using the Extension from Claude Desktop (MCP Client) +## MCP Server Management -To use the VSCode MCP Server with Claude Desktop, you need to configure Claude Desktop to connect to the MCP server running in VSCode. Since the implementation of the MCP server uses SSE transport, and Claude Desktop only supports stdio transport, you need to use a [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy) to bridge the communication between the two. +The MCP Server status can now be managed directly from the Command Palette: -1. **Install MCP Proxy:** +1. **Stop MCP Server** (`mcpServer.stopServer`): Stops the currently running MCP Server. +2. **Start MCP Server** (`mcpServer.startServer`): Starts the server on the configured or next available port. - - Option 1: With uv (recommended) +These commands help manage server lifecycle dynamically, without requiring a VSCode restart. - ``` - uv tool install mcp-proxy - ``` +## Dynamic Port Configuration - - Option 2: With pipx (alternative) +If the port is already in use, the extension will suggest the next available port and apply it dynamically. Logs reflecting the selected port can be found in the **MCP Server Logs** output channel. - ``` - pipx install mcp-proxy - ``` +Users can configure or change the MCP Server's port at runtime using the Command Palette: -2. **Configure Claude Desktop:** +1. Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS). +2. Search for `Set MCP Server Port`. +3. Enter the desired port number in the input box and confirm. - - Open Claude Desktop and navigate to the **File** > **Settings** > **Developer** tab. - - Click **Edit Config** to open the config file, launch your desired editor to modify the config file contents. - - Add a new entry to **mcpServers** with the following details: - - ```json - { - "mcpServers": { - "vscode": { - "command": "mcp-proxy", - "args": ["http://127.0.0.1:6010/sse"] - } - } - } - ``` +The server will dynamically restart on the newly selected port, and the configuration will be updated for future sessions. -3. **Restart Claude Desktop:** +The HTTP server port can also be set via VSCode settings: + +1. Open VSCode settings (`File > Preferences > Settings` or `Ctrl+,`). +2. Search for `mcpServer.port`. +3. Set your desired port number. +4. Restart VSCode for the changes to take effect. + +## MCP Server Automatic Startup + +The MCP Server starts automatically on VSCode activation by default. To disable this feature: + +1. Open VSCode settings (`File > Preferences > Settings` or `Ctrl+,`). +2. Search for `mcpServer.startOnActivate`. +3. Toggle the setting to `false`. + +This can be useful if you prefer to start the server manually using the `Start MCP Server` command. + +## Extension Development - - You **must** restart Claude Desktop for the changes to take effect by using the **File** > **Exit** option. - - NOTE: This is different than just closing the window or using **File** > **Close**, which leaves the application running in the background. - - After existing and then starting again, Claude Desktop should now be able to connect to the MCP server running in VSCode. +Steps for developing and debugging the extension, source code available at [GitHub](https://github.com/microsoft/semanticworkbench/tree/main/mcp-servers/mcp-server-vscode). -## Debugging the Extension +### Prerequisites + +1. **Clone the Repository:** + Clone the Semantic Workbench repository to your local machine: + + ```bash + git clone https://github.com/microsoft/semanticworkbench.git + ``` + +2. **Navigate to the Project Directory:** + + ```bash + cd semanticworkbench/mcp-servers/mcp-server-vscode + ``` + +3. **Install Dependencies:** + Ensure you have Node.js (v16 or higher) and pnpm installed. Then, from the project directory, run: + + ```bash + pnpm install + ``` + +4. **Package the Extension:** + To package the extension, execute: + ```bash + pnpm run package-extension + ``` + This will generate a `.vsix` file in the project root. + +### Installing the Extension Locally + +1. **Open Your Main VSCode Instance:** + + Launch your main VSCode (outside the Extension Development Host). + +2. **Install the VSIX Package:** + + - Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) to open the Command Palette. + - Type and select "Extensions: Install from VSIX...". + - Navigate to and select the generated .vsix file. + +3. **Reload and Verify:** + + After installation, reload VSCode (via "Developer: Reload Window" from the Command Palette) and verify that the extension is active. Check the "MCP Server Logs" output channel to see logs confirming that the MCP server has started and is listening on the configured port (default: 6010, or the next available one). + +### Debugging the Extension 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:** On activation, the extension: - - Starts the MCP server which registers the `code_checker` tool. - - Sets up an Express HTTP server on port **6010** with: - - **GET `/sse`:** To establish an SSE connection (external clients connect here). - - **POST `/messages`:** To process incoming MCP protocol messages. - - Outputs all activity to the **"MCP Server Logs"** channel (which will be auto-shown). + - Starts the MCP server which registers the `code_checker` tool. + - Sets up an Express HTTP server on port **6010** with: + - **GET `/sse`:** To establish an SSE connection (external clients connect here). + - **POST `/messages`:** To process incoming MCP protocol messages. + - Outputs all activity to the **"MCP Server Logs"** channel (which will be auto-shown). -## Installing the Extension Locally +### Installing the Extension Locally 1. **Open Your Main VSCode Instance:** - Launch your main VSCode (outside the Extension Development Host). + Launch your main VSCode (outside the Extension Development Host). 2. **Install the VSIX Package:** - - Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) to open the Command Palette. - - Type and select "Extensions: Install from VSIX...". - - Navigate to and select the generated .vsix file. + - Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) to open the Command Palette. + - Type and select "Extensions: Install from VSIX...". + - Navigate to and select the generated .vsix file. 3. **Reload and Verify:** - After installation, reload VSCode (via "Developer: Reload Window" from the Command Palette) and verify that the extension is active. Check the "MCP Server Logs" output channel to see logs confirming that the MCP server has started and is listening on port 6010. + After installation, reload VSCode (via "Developer: Reload Window" from the Command Palette) and verify that the extension is active. Check the "MCP Server Logs" output channel to see logs confirming that the MCP server has started and is listening on port 6010. ## Testing the MCP Server @@ -189,9 +240,3 @@ curl -X POST "http://127.0.0.1:6010/messages?sessionId=your-session-id" \ ``` If everything is configured correctly, the MCP server should process your initialization message without errors. - -## Following extension guidelines - -Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension. - -- [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines) diff --git a/mcp-servers/mcp-server-vscode/eslint.config.mjs b/mcp-servers/mcp-server-vscode/eslint.config.mjs index 340b21e3..9220b07d 100644 --- a/mcp-servers/mcp-server-vscode/eslint.config.mjs +++ b/mcp-servers/mcp-server-vscode/eslint.config.mjs @@ -1,51 +1,57 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import tsParser from "@typescript-eslint/parser"; -import importPlugin from "eslint-plugin-import"; +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import importPlugin from 'eslint-plugin-import'; -export default [{ - files: ["**/*.ts"], -}, { - ignorePatterns: ['dist', '.*.js', '*.config.js', 'node_modules'], - - plugins: { - "@typescript-eslint": typescriptEslint, - "import": importPlugin, +export default [ + { + files: ['**/*.ts'], }, + { + ignorePatterns: ['dist', '.*.js', '*.config.js', 'node_modules'], - languageOptions: { - parser: tsParser, - ecmaVersion: 2022, - sourceType: "module", - }, + plugins: { + '@typescript-eslint': typescriptEslint, + import: importPlugin, + }, + + languageOptions: { + parser: tsParser, + ecmaVersion: 2022, + sourceType: 'module', + }, - rules: { - "@typescript-eslint/naming-convention": ["warn", { - selector: "import", - format: ["camelCase", "PascalCase"], - }], - '@typescript-eslint/brace-style': ['off'], - '@typescript-eslint/space-before-function-paren': [ - 'error', - { anonymous: 'always', named: 'never', asyncArrow: 'always' }, - ], - '@typescript-eslint/semi': ['error', 'always'], - '@typescript-eslint/triple-slash-reference': ['error', { types: 'prefer-import' }], - '@typescript-eslint/indent': ['off'], - '@typescript-eslint/comma-dangle': ['error', 'always-multiline'], - '@typescript-eslint/strict-boolean-expressions': 'off', - '@typescript-eslint/member-delimiter-style': [ - 'error', - { - multiline: { - delimiter: 'semi', - requireLast: true, + rules: { + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'import', + format: ['camelCase', 'PascalCase'], }, - singleline: { - delimiter: 'semi', - requireLast: false, + ], + '@typescript-eslint/brace-style': ['off'], + '@typescript-eslint/space-before-function-paren': [ + 'error', + { anonymous: 'always', named: 'never', asyncArrow: 'always' }, + ], + '@typescript-eslint/semi': ['error', 'always'], + '@typescript-eslint/triple-slash-reference': ['error', { types: 'prefer-import' }], + '@typescript-eslint/indent': ['off'], + '@typescript-eslint/comma-dangle': ['error', 'always-multiline'], + '@typescript-eslint/strict-boolean-expressions': 'off', + '@typescript-eslint/member-delimiter-style': [ + 'error', + { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, }, - }, - ], - '@typescript-eslint/explicit-function-return-type': 'off', + ], + '@typescript-eslint/explicit-function-return-type': 'off', + }, }, -}]; +]; diff --git a/mcp-servers/mcp-server-vscode/images/icon.png b/mcp-servers/mcp-server-vscode/images/icon.png new file mode 100644 index 00000000..ddd56cf3 Binary files /dev/null and b/mcp-servers/mcp-server-vscode/images/icon.png differ diff --git a/mcp-servers/mcp-server-vscode/package.json b/mcp-servers/mcp-server-vscode/package.json index 69eb3998..df5b9c51 100644 --- a/mcp-servers/mcp-server-vscode/package.json +++ b/mcp-servers/mcp-server-vscode/package.json @@ -1,56 +1,99 @@ { - "name": "mcp-server-vscode", - "displayName": "VSCode MCP Server", - "publisher": "semantic-workbench-team", - "description": "VSCode tools and resources as a Model Context Protocol (MCP) server in a VSCode extension.", - "version": "0.0.1", - "engines": { - "vscode": "^1.96.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/semanticworkbench" - }, - "categories": [ - "Other" - ], - "activationEvents": [ - "*" - ], - "main": "./dist/extension.js", - "scripts": { - "vscode:prepublish": "pnpm run package", - "compile": "webpack", - "watch": "webpack --watch", - "package": "webpack --mode production --devtool hidden-source-map", - "compile-tests": "tsc -p . --outDir out", - "package-extension": "cross-env npm_config_user_agent=pnpm vsce package", - "watch-tests": "tsc -p . -w --outDir out", - "pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint", - "lint": "eslint src", - "test": "vscode-test" - }, - "devDependencies": { - "@types/express": "^5.0.0", - "@types/mocha": "^10.0.10", - "@types/node": "~22.13.1", - "@types/vscode": "^1.96.0", - "@typescript-eslint/eslint-plugin": "^8.22.0", - "@typescript-eslint/parser": "^8.22.0", - "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.2.2", - "cross-env": "^7.0.3", - "eslint": "^9.19.0", - "eslint-plugin-import": "^2.31.0", - "ts-loader": "^9.5.2", - "typescript": "^5.7.3", - "webpack": "^5.97.1", - "webpack-cli": "^6.0.1" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.4.1", - "dedent": "^1.5.3", - "express": "^4.21.2" - } + "name": "mcp-server-vscode", + "displayName": "VSCode MCP Server", + "publisher": "SemanticWorkbenchTeam", + "description": "VSCode tools and resources as a Model Context Protocol (MCP) server in a VSCode extension.", + "version": "0.0.9", + "type": "commonjs", + "icon": "images/icon.png", + "engines": { + "vscode": "^1.96.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/semanticworkbench" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onStartupFinished" + ], + "contributes": { + "commands": [ + { + "command": "mcpServer.stopServer", + "title": "MCP Server: Stop Server" + }, + { + "command": "mcpServer.startServer", + "title": "MCP Server: Start Server" + }, + { + "command": "mcpServer.setPort", + "title": "MCP Server: Set Port" + } + ], + "configuration": { + "type": "object", + "properties": { + "mcpServer.startOnActivate": { + "type": "boolean", + "default": true, + "description": "Determines if the MCP Server should start automatically on VSCode activation." + }, + "mcpServer.port": { + "type": "number", + "default": 6010, + "description": "The port that the MCP Server listens on. Set in case of conflicts or custom configurations." + } + } + } + }, + "main": "./dist/extension.js", + "scripts": { + "vscode:prepublish": "pnpm run package", + "compile": "webpack", + "watch": "webpack --watch", + "package": "webpack --mode production --devtool hidden-source-map", + "compile-tests": "tsc -p . --outDir out", + "package-extension": "cross-env npm_config_user_agent=pnpm vsce package", + "watch-tests": "tsc -p . -w --outDir out", + "pretest": "pnpm run compile-tests && pnpm run compile && pnpm run lint", + "lint": "eslint src", + "test": "vscode-test" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/mocha": "^10.0.10", + "@types/node": "~22.13.1", + "@types/vscode": "^1.96.0", + "@typescript-eslint/eslint-plugin": "^8.22.0", + "@typescript-eslint/parser": "^8.22.0", + "@vscode/test-cli": "^0.0.10", + "@vscode/test-electron": "^2.4.1", + "@vscode/vsce": "^3.2.2", + "cross-env": "^7.0.3", + "eslint": "^9.19.0", + "eslint-plugin-import": "^2.31.0", + "prettier": "^2.8.8", + "ts-loader": "^9.5.2", + "typescript": "^5.7.3", + "webpack": "^5.97.1", + "webpack-cli": "^6.0.1" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.4.1", + "dedent": "^1.5.3", + "express": "^4.21.2", + "get-port": "^7.1.0" + }, + "eslintConfig": { + "plugins": [ + "prettier" + ], + "rules": { + "prettier/prettier": "error" + } + } } diff --git a/mcp-servers/mcp-server-vscode/pnpm-lock.yaml b/mcp-servers/mcp-server-vscode/pnpm-lock.yaml index dfa807ef..eab1c9a5 100644 --- a/mcp-servers/mcp-server-vscode/pnpm-lock.yaml +++ b/mcp-servers/mcp-server-vscode/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: express: specifier: ^4.21.2 version: 4.21.2 + get-port: + specifier: ^7.1.0 + version: 7.1.0 devDependencies: '@types/express': specifier: ^5.0.0 @@ -54,6 +57,9 @@ importers: eslint-plugin-import: specifier: ^2.31.0 version: 2.31.0(@typescript-eslint/parser@8.23.0(eslint@9.19.0)(typescript@5.7.3))(eslint@9.19.0) + prettier: + specifier: ^2.8.8 + version: 2.8.8 ts-loader: specifier: ^9.5.2 version: 9.5.2(typescript@5.7.3)(webpack@5.97.1) @@ -1214,6 +1220,10 @@ packages: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} + get-port@7.1.0: + resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} + engines: {node: '>=16'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1952,6 +1962,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -3994,6 +4009,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-port@7.1.0: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -4767,6 +4784,8 @@ snapshots: prelude-ls@1.2.1: {} + prettier@2.8.8: {} + process-nextick-args@2.0.1: {} proxy-addr@2.0.7: diff --git a/mcp-servers/mcp-server-vscode/prettier.config.cjs b/mcp-servers/mcp-server-vscode/prettier.config.cjs new file mode 100644 index 00000000..3ae5e9c4 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/prettier.config.cjs @@ -0,0 +1,8 @@ +module.exports = { + bracketSpacing: true, + printWidth: 120, + singleQuote: true, + tabWidth: 4, + trailingComma: 'all', + useTabs: false, +}; diff --git a/mcp-servers/mcp-server-vscode/src/extension.d.ts b/mcp-servers/mcp-server-vscode/src/extension.d.ts index 08bd2584..ae7d8173 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 const activate: (context: vscode.ExtensionContext) => void; +import * as vscode from 'vscode'; +export declare const activate: (context: vscode.ExtensionContext) => Promise; 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 ecff4705..85a4eb16 100644 --- a/mcp-servers/mcp-server-vscode/src/extension.ts +++ b/mcp-servers/mcp-server-vscode/src/extension.ts @@ -1,136 +1,323 @@ -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 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(); - - // 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 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", - })), - }; - } - ); - - // 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); - } - }); +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 packageJson from '../package.json'; +import { codeCheckerTool } from './tools/code_checker'; +import { + listDebugSessions, + listDebugSessionsSchema, + startDebugSession, + startDebugSessionSchema, + stopDebugSession, + stopDebugSessionSchema, +} from './tools/debug_tools'; +import { focusEditorTool } from './tools/focus_editor'; +import { resolvePort } from './utils/port'; + +const extensionName = 'vscode-mcp-server'; +const extensionDisplayName = 'VSCode MCP Server'; + +export const activate = async (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(); + + // Initialize the MCP server instance + const mcpServer = new McpServer({ + name: extensionName, + version: packageJson.version, + }); + + // 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', + })), + }; + }, + ); - // 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)}` + // Register 'focus_editor' tool + mcpServer.tool( + 'focus_editor', + dedent` + Open the specified file in the VSCode editor and navigate to a specific line and column. + Use this tool to bring a file into focus and position the editor's cursor where desired. + Note: This tool operates on the editor visual environment so that the user can see the file. It does not return the file contents in the tool call result. + `.trim(), + { + filePath: z.string().describe('The absolute path to the file to focus in the editor.'), + line: z.number().int().min(0).default(0).describe('The line number to navigate to (default: 0).'), + column: z.number().int().min(0).default(0).describe('The column position to navigate to (default: 0).'), + startLine: z.number().int().min(0).optional().describe('The starting line number for highlighting.'), + startColumn: z.number().int().min(0).optional().describe('The starting column number for highlighting.'), + endLine: z.number().int().min(0).optional().describe('The ending line number for highlighting.'), + endColumn: z.number().int().min(0).optional().describe('The ending column number for highlighting.'), + }, + async (params: { filePath: string; line?: number; column?: number }) => { + const result = await focusEditorTool(params); + return result; + }, ); - 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); - } + // FIXME: This doesn't return results yet + // // Register 'search_symbol' tool + // mcpServer.tool( + // 'search_symbol', + // dedent` + // Search for a symbol within the workspace. + // - Tries to resolve the definition via VSCode’s "Go to Definition". + // - If not found, searches the entire workspace for the text, similar to Ctrl+Shift+F. + // `.trim(), + // { + // query: z.string().describe('The symbol or text to search for.'), + // useDefinition: z.boolean().default(true).describe("Whether to use 'Go to Definition' as the first method."), + // maxResults: z.number().default(50).describe('Maximum number of global search results to return.'), + // openFile: z.boolean().default(false).describe('Whether to open the found file in the editor.'), + // }, + // async (params: { query: string; useDefinition?: boolean; maxResults?: number; openFile?: boolean }) => { + // const result = await searchSymbolTool(params); + // return { + // ...result, + // content: [ + // { + // text: JSON.stringify(result), + // type: 'text', + // }, + // ], + // }; + // }, + // ); + + // Register 'list_debug_sessions' tool + mcpServer.tool( + 'list_debug_sessions', + 'List all active debug sessions in the workspace.', + listDebugSessionsSchema.shape, // No parameters required + async () => { + const result = await listDebugSessions(); + return { + ...result, + content: result.content.map((item) => ({ type: 'text', text: JSON.stringify(item.json) })), + }; + }, + ); + + // Register 'start_debug_session' tool + mcpServer.tool( + 'start_debug_session', + 'Start a new debug session with the provided configuration.', + startDebugSessionSchema.shape, + async (params) => { + const result = await startDebugSession(params); + return { + ...result, + content: result.content.map((item) => ({ + ...item, + type: 'text' as const, + })), + }; + }, + ); + + // Register 'stop_debug_session' tool + + // Register 'restart_debug_session' tool + mcpServer.tool( + 'restart_debug_session', + 'Restart a debug session by stopping it and then starting it with the provided configuration.', + startDebugSessionSchema.shape, // using the same schema as 'start_debug_session' + async (params) => { + // Stop current session using the provided session name + await stopDebugSession({ sessionName: params.configuration.name }); + + // Then start a new debug session with the given configuration + const result = await startDebugSession(params); + return { + ...result, + content: result.content.map((item) => ({ + ...item, + type: 'text' as const, + })), + }; + }, + ); + mcpServer.tool( + 'stop_debug_session', + 'Stop all debug sessions that match the provided session name.', + stopDebugSessionSchema.shape, + async (params) => { + const result = await stopDebugSession(params); + return { + ...result, + content: result.content.map((item) => ({ + ...item, + type: 'text' as const, + })), + }; + }, + ); + + // Set up an Express app to handle SSE connections + const app = express(); + const mcpConfig = vscode.workspace.getConfiguration('mcpServer'); + const port = await resolvePort(mcpConfig.get('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); + } + }); + + // 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.'); + } + }); + + // Create and start the HTTP server + const server = http.createServer(app); + function startServer(port: number): void { + 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(); + }, + }); + } + const startOnActivate = mcpConfig.get('startOnActivate', true); + if (startOnActivate) { + startServer(port); } else { - res.status(500).send("SSE Transport not initialized."); - outputChannel.appendLine( - "POST /messages failed: SSE Transport not initialized." - ); + outputChannel.appendLine('MCP Server startup disabled by configuration.'); } - }); - // 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` + // COMMAND PALETTE COMMAND: Stop the MCP Server + context.subscriptions.push( + vscode.commands.registerCommand('mcpServer.stopServer', () => { + if (!server.listening) { + vscode.window.showWarningMessage('MCP Server is not running.'); + outputChannel.appendLine('Attempted to stop the MCP Server, but it is not running.'); + return; + } + server.close(() => { + outputChannel.appendLine('MCP Server stopped.'); + vscode.window.showInformationMessage('MCP Server stopped.'); + }); + }), ); - }); - // Add disposal to shut down the HTTP server and output channel on extension deactivation - context.subscriptions.push({ - dispose: () => { - server.close(); - outputChannel.dispose(); - }, - }); + // COMMAND PALETTE COMMAND: Start the MCP Server + context.subscriptions.push( + vscode.commands.registerCommand('mcpServer.startServer', async () => { + if (server.listening) { + vscode.window.showWarningMessage('MCP Server is already running.'); + outputChannel.appendLine('Attempted to start the MCP Server, but it is already running.'); + return; + } + const newPort = await resolvePort(mcpConfig.get('port', 6010)); + startServer(newPort); + outputChannel.appendLine(`MCP Server started on port ${newPort}.`); + vscode.window.showInformationMessage(`MCP Server started on port ${newPort}.`); + }), + ); - outputChannel.appendLine(`${extensionDisplayName} activated.`); -} + // COMMAND PALETTE COMMAND: Set the MCP server port and restart the server + context.subscriptions.push( + vscode.commands.registerCommand('mcpServer.setPort', async () => { + const newPortInput = await vscode.window.showInputBox({ + prompt: 'Enter new port number for the MCP Server:', + value: String(port), + validateInput: (input) => { + const num = Number(input); + if (isNaN(num) || num < 1 || num > 65535) { + return 'Please enter a valid port number (1-65535).'; + } + return null; + }, + }); + if (newPortInput && newPortInput.trim().length > 0) { + const newPort = Number(newPortInput); + // Update the configuration so that subsequent startups use the new port + await vscode.workspace + .getConfiguration('mcpServer') + .update('port', newPort, vscode.ConfigurationTarget.Global); + // Restart the server: close existing server and start a new one + server.close(); + startServer(newPort); + outputChannel.appendLine(`MCP Server restarted on port ${newPort}`); + vscode.window.showInformationMessage(`MCP Server restarted on port ${newPort}`); + } + }), + ); + + 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/test/extension.test.ts b/mcp-servers/mcp-server-vscode/src/test/extension.test.ts index 4ca0ab41..04181126 100644 --- a/mcp-servers/mcp-server-vscode/src/test/extension.test.ts +++ b/mcp-servers/mcp-server-vscode/src/test/extension.test.ts @@ -6,10 +6,10 @@ import * as vscode from 'vscode'; // import * as myExtension from '../../extension'; suite('Extension Test Suite', () => { - vscode.window.showInformationMessage('Start all tests.'); + vscode.window.showInformationMessage('Start all tests.'); - test('Sample test', () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); - }); + test('Sample test', () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); }); diff --git a/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts b/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts index 513d0728..c2f4becc 100644 --- a/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts +++ b/mcp-servers/mcp-server-vscode/src/tools/code_checker.ts @@ -5,29 +5,29 @@ import { DiagnosticSeverity, languages } from 'vscode'; * * @param 'Warning' - Minimum severity level to include (default is Warning). */ -export const codeCheckerTool = ( - severityLevel: DiagnosticSeverity = DiagnosticSeverity.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)) + .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 || "" - })) + 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 }; + 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 }; -} + return { content: [{ type: 'text', text: JSON.stringify(aggregated, null, 2) }], isError: false }; +}; diff --git a/mcp-servers/mcp-server-vscode/src/tools/debug_tools.d.ts b/mcp-servers/mcp-server-vscode/src/tools/debug_tools.d.ts new file mode 100644 index 00000000..f974eb6b --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/debug_tools.d.ts @@ -0,0 +1,81 @@ +import * as vscode from 'vscode'; +import { z } from 'zod'; +export declare const listDebugSessions: () => { + content: { + type: string; + json: { + sessions: { + id: string; + name: string; + configuration: vscode.DebugConfiguration; + }[]; + }; + }[]; + isError: boolean; +}; +export declare const listDebugSessionsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>; +export declare const startDebugSession: (params: { + workspaceFolder: string; + configuration: { + type: string; + request: string; + name: string; + [key: string]: any; + }; +}) => Promise<{ + content: { + type: string; + text: string; + }[]; + isError: boolean; +}>; +export declare const startDebugSessionSchema: z.ZodObject<{ + workspaceFolder: z.ZodString; + configuration: z.ZodObject<{ + type: z.ZodString; + request: z.ZodString; + name: z.ZodString; + }, "passthrough", z.ZodTypeAny, z.objectOutputType<{ + type: z.ZodString; + request: z.ZodString; + name: z.ZodString; + }, z.ZodTypeAny, "passthrough">, z.objectInputType<{ + type: z.ZodString; + request: z.ZodString; + name: z.ZodString; + }, z.ZodTypeAny, "passthrough">>; +}, "strip", z.ZodTypeAny, { + workspaceFolder: string; + configuration: { + name: string; + type: string; + request: string; + } & { + [k: string]: unknown; + }; +}, { + workspaceFolder: string; + configuration: { + name: string; + type: string; + request: string; + } & { + [k: string]: unknown; + }; +}>; +export declare const stopDebugSession: (params: { + sessionName: string; +}) => Promise<{ + content: { + type: string; + text: string; + }[]; + isError: boolean; +}>; +export declare const stopDebugSessionSchema: z.ZodObject<{ + sessionName: z.ZodString; +}, "strip", z.ZodTypeAny, { + sessionName: string; +}, { + sessionName: string; +}>; diff --git a/mcp-servers/mcp-server-vscode/src/tools/debug_tools.ts b/mcp-servers/mcp-server-vscode/src/tools/debug_tools.ts new file mode 100644 index 00000000..ebcf5bae --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/debug_tools.ts @@ -0,0 +1,135 @@ +import * as vscode from 'vscode'; +import { z } from 'zod'; + +/** Maintain a list of active debug sessions. */ +const activeSessions: vscode.DebugSession[] = []; + +// Track new debug sessions as they start. +vscode.debug.onDidStartDebugSession((session) => { + activeSessions.push(session); +}); + +// Remove debug sessions as they terminate. +vscode.debug.onDidTerminateDebugSession((session) => { + const index = activeSessions.indexOf(session); + if (index >= 0) { + activeSessions.splice(index, 1); + } +}); + +/** + * List all active debug sessions in the workspace. + * + * Exposes debug session information, including each session's ID, name, and associated launch configuration. + */ +export const listDebugSessions = () => { + // Retrieve all active debug sessions using the activeSessions array. + const sessions = activeSessions.map((session: vscode.DebugSession) => ({ + id: session.id, + name: session.name, + configuration: session.configuration, + })); + + // Return session list + return { + content: [ + { + type: 'json', + json: { sessions }, + }, + ], + isError: false, + }; +}; + +// Zod schema for validating tool parameters (none for this tool). +export const listDebugSessionsSchema = z.object({}); + +/** + * Start a new debug session using the provided configuration. + * + * @param params - Object containing workspaceFolder and configuration details. + */ +export const startDebugSession = async (params: { + workspaceFolder: string; + configuration: { type: string; request: string; name: string; [key: string]: any }; +}) => { + const { workspaceFolder, configuration } = params; + // Ensure that workspace folders exist and are accessible. + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + throw new Error('No workspace folders are currently open.'); + } + + const folder = workspaceFolders.find((f) => f.uri?.fsPath === workspaceFolder); + if (!folder) { + throw new Error(`Workspace folder '${workspaceFolder}' not found.`); + } + + const success = await vscode.debug.startDebugging(folder, configuration); + + if (!success) { + throw new Error(`Failed to start debug session '${configuration.name}'.`); + } + + return { + content: [{ type: 'text', text: `Debug session '${configuration.name}' started successfully.` }], + isError: false, + }; +}; + +// Zod schema for validating start_debug_session parameters. +export const startDebugSessionSchema = z.object({ + workspaceFolder: z.string().describe('The workspace folder where the debug session should start.'), + configuration: z + .object({ + type: z.string().describe("Type of the debugger (e.g., 'node', 'python', etc.)."), + request: z.string().describe("Type of debug request (e.g., 'launch' or 'attach')."), + name: z.string().describe('Name of the debug session.'), + }) + .passthrough() + .describe('The debug configuration object.'), +}); + +/** + * Stop debug sessions that match the provided session name. + * + * @param params - Object containing the sessionName to stop. + */ +export const stopDebugSession = async (params: { sessionName: string }) => { + const { sessionName } = params; + // Filter active sessions to find matching sessions. + const matchingSessions = activeSessions.filter((session: vscode.DebugSession) => session.name === sessionName); + + if (matchingSessions.length === 0) { + return { + content: [ + { + type: 'text', + text: `No debug session(s) found with name '${sessionName}'.`, + }, + ], + isError: true, + }; + } + + // Stop each matching debug session. + for (const session of matchingSessions) { + await vscode.debug.stopDebugging(session); + } + + return { + content: [ + { + type: 'text', + text: `Stopped debug session(s) with name '${sessionName}'.`, + }, + ], + isError: false, + }; +}; + +// Zod schema for validating stop_debug_session parameters. +export const stopDebugSessionSchema = z.object({ + sessionName: z.string().describe('The name of the debug session(s) to stop.'), +}); diff --git a/mcp-servers/mcp-server-vscode/src/tools/focus_editor.d.ts b/mcp-servers/mcp-server-vscode/src/tools/focus_editor.d.ts new file mode 100644 index 00000000..280c2ca9 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/focus_editor.d.ts @@ -0,0 +1,15 @@ +export declare const focusEditorTool: ({ filePath, line, column, startLine, startColumn, endLine, endColumn, }: { + filePath: string; + line?: number; + column?: number; + startLine?: number; + startColumn?: number; + endLine?: number; + endColumn?: number; +}) => Promise<{ + success: boolean; + content: { + type: "text"; + text: string; + }[]; +}>; diff --git a/mcp-servers/mcp-server-vscode/src/tools/focus_editor.ts b/mcp-servers/mcp-server-vscode/src/tools/focus_editor.ts new file mode 100644 index 00000000..d83221ee --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/focus_editor.ts @@ -0,0 +1,65 @@ +import * as vscode from 'vscode'; + +export const focusEditorTool = async ({ + filePath, + line = 0, + column = 0, + startLine, + startColumn, + endLine, + endColumn, +}: { + filePath: string; + line?: number; + column?: number; + startLine?: number; + startColumn?: number; + endLine?: number; + endColumn?: number; +}): Promise<{ + success: boolean; + content: { type: 'text'; text: string }[]; +}> => { + const uri = vscode.Uri.file(filePath); + const document = await vscode.workspace.openTextDocument(uri); // Open the document + const editor = await vscode.window.showTextDocument(document); // Show it in the editor + + // Highlight range if all range parameters are provided + if ( + typeof startLine === 'number' && + typeof startColumn === 'number' && + typeof endLine === 'number' && + typeof endColumn === 'number' && + // Ensure that a valid range is provided by checking that the start and end are not both zeros + (startLine !== 0 || startColumn !== 0 || endLine !== 0 || endColumn !== 0) + ) { + const start = new vscode.Position(startLine, startColumn); + const end = new vscode.Position(endLine, endColumn); + editor.selection = new vscode.Selection(start, end); + editor.revealRange(new vscode.Range(start, end), vscode.TextEditorRevealType.InCenter); + return { + success: true, + content: [ + { + type: 'text' as const, + text: `Focused file: ${filePath} with highlighted range from line ${startLine}, column ${startColumn} to line ${endLine}, column ${endColumn}`, + }, + ], + }; + } else { + // Move the cursor to the specified position + const position = new vscode.Position(line, column); + editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter); + editor.selection = new vscode.Selection(position, position); + + return { + success: true, + content: [ + { + type: 'text' as const, + text: `Focused file: ${filePath} at line ${line}, column ${column}`, + }, + ], + }; + } +}; diff --git a/mcp-servers/mcp-server-vscode/src/tools/search_symbol.d.ts b/mcp-servers/mcp-server-vscode/src/tools/search_symbol.d.ts new file mode 100644 index 00000000..b662648b --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/search_symbol.d.ts @@ -0,0 +1,20 @@ +export declare function searchSymbolTool({ query, useDefinition, maxResults, openFile, }: { + query: string; + useDefinition?: boolean; + maxResults?: number; + openFile?: boolean; +}): Promise<{ + definition: { + file: string; + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; + snippet: string; + } | null; + globalSearch: Array<{ + file: string; + line: number; + snippet: string; + }>; +}>; diff --git a/mcp-servers/mcp-server-vscode/src/tools/search_symbol.ts b/mcp-servers/mcp-server-vscode/src/tools/search_symbol.ts new file mode 100644 index 00000000..aacbc51b --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/tools/search_symbol.ts @@ -0,0 +1,94 @@ +import * as vscode from 'vscode'; +import { focusEditorTool } from './focus_editor'; + +export async function searchSymbolTool({ + query, + useDefinition = true, + maxResults = 50, + openFile = false, // Optional: Open files after search +}: { + query: string; + useDefinition?: boolean; + maxResults?: number; + openFile?: boolean; +}) { + const results: { + definition: { + file: string; + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; + snippet: string; + } | null; + globalSearch: Array<{ file: string; line: number; snippet: string }>; + } = { definition: null, globalSearch: [] }; + + // Try "Go to Definition" + if (useDefinition && vscode.window.activeTextEditor) { + const editor = vscode.window.activeTextEditor; + const position = editor.selection.active; + const uri = editor.document.uri; + + const definitionResults = await vscode.commands.executeCommand( + 'vscode.executeDefinitionProvider', + uri, + position, + ); + + if (definitionResults && definitionResults.length > 0) { + const def = definitionResults[0]; + results.definition = { + file: def.uri.fsPath, + startLine: def.range.start.line, + startColumn: def.range.start.character, + endLine: def.range.end.line, + endColumn: def.range.end.character, + snippet: def.range.start.line === def.range.end.line ? editor.document.getText(def.range) : '', + }; + + // Reuse `focusEditorTool` if applicable + if (openFile) { + await focusEditorTool({ + filePath: def.uri.fsPath, + startLine: def.range.start.line, + startColumn: def.range.start.character, + endLine: def.range.end.line, + endColumn: def.range.end.character, + }); + } + } + } + + // Perform a global text search + const globalSearchResults: Array = []; + await vscode.commands.executeCommand<{ uri: vscode.Uri; ranges: vscode.Range[]; preview: { text: string } }[]>( + 'vscode.executeWorkspaceSymbolProvider', + query, + ({ uri, ranges, preview }: { uri: vscode.Uri; ranges: vscode.Range[]; preview: { text: string } }) => { + const match = { + file: uri.fsPath, + line: ranges[0].start.line, + snippet: preview.text.trim(), + }; + + if (globalSearchResults.length < maxResults) { + globalSearchResults.push({ + file: match.file, // Correct the key to 'file' + line: match.line, // Correct key/logic + snippet: match.snippet, // Correct key/logic + }); + } + }, + ); + + results.globalSearch = globalSearchResults; + + // Open the first global search result if requested + if (openFile && globalSearchResults.length > 0) { + const firstMatch = globalSearchResults[0]; + await focusEditorTool({ filePath: firstMatch.file, line: firstMatch.line, column: 0 }); + } + + return results; +} diff --git a/mcp-servers/mcp-server-vscode/src/utils/port.d.ts b/mcp-servers/mcp-server-vscode/src/utils/port.d.ts new file mode 100644 index 00000000..929f01a2 --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/utils/port.d.ts @@ -0,0 +1 @@ +export declare function resolvePort(desiredPort: number): Promise; diff --git a/mcp-servers/mcp-server-vscode/src/utils/port.ts b/mcp-servers/mcp-server-vscode/src/utils/port.ts new file mode 100644 index 00000000..0e6b49ef --- /dev/null +++ b/mcp-servers/mcp-server-vscode/src/utils/port.ts @@ -0,0 +1,28 @@ +import getPort from 'get-port'; +import * as vscode from 'vscode'; + +export async function resolvePort(desiredPort: number): Promise { + // Try to get the desired port or the next available port + const availablePort = await getPort({ port: desiredPort }); + + // If the available port is not the same as the desired port, prompt the user + if (availablePort !== desiredPort) { + const userInput = await vscode.window.showInputBox({ + prompt: `Port ${desiredPort} is in use. Enter a new port or press Enter to use the available port (${availablePort}):`, + value: String(availablePort), + validateInput: (input) => { + const num = Number(input); + if (isNaN(num) || num < 1 || num > 65535) { + return 'Please enter a valid port number (1-65535).'; + } + return null; + }, + }); + if (userInput && userInput.trim().length > 0) { + const newPort = Number(userInput); + return resolvePort(newPort); + } + return availablePort; + } + return availablePort; +} diff --git a/mcp-servers/mcp-server-vscode/tsconfig.json b/mcp-servers/mcp-server-vscode/tsconfig.json index fe6395d0..7edf6228 100644 --- a/mcp-servers/mcp-server-vscode/tsconfig.json +++ b/mcp-servers/mcp-server-vscode/tsconfig.json @@ -1,34 +1,35 @@ { - "compilerOptions": { - "module": "Node16", - "target": "ES2022", - "lib": ["ES2022"], - "rootDir": "src", - "strict": true, - "useDefineForClassFields": true, - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node16", - "resolveJsonModule": true, - "isolatedModules": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "inlineSourceMap": true, - "inlineSources": true, - "removeComments": true, - "downlevelIteration": true, - "composite": true - }, - "include": ["src"] + "compilerOptions": { + "module": "ESNext", + "target": "ES2022", + "lib": ["ES2022"], + "rootDir": "src", + "strict": true, + "useDefineForClassFields": true, + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "inlineSources": true, + "removeComments": true, + "downlevelIteration": true, + "composite": true, + "types": ["mocha", "node"] + }, + "include": ["src"] } diff --git a/mcp-servers/mcp-server-vscode/vsc-extension-quickstart.md b/mcp-servers/mcp-server-vscode/vsc-extension-quickstart.md index e2d793c2..b1212809 100644 --- a/mcp-servers/mcp-server-vscode/vsc-extension-quickstart.md +++ b/mcp-servers/mcp-server-vscode/vsc-extension-quickstart.md @@ -2,45 +2,45 @@ ## What's in the folder -- This folder contains all of the files necessary for your extension. -- `package.json` - this is the manifest file in which you declare your extension and command. - - The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -- `src/extension.ts` - this is the main file where you will provide the implementation of your command. - - The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - - We pass the function containing the implementation of the command as the second parameter to `registerCommand`. +- This folder contains all of the files necessary for your extension. +- `package.json` - this is the manifest file in which you declare your extension and command. + - The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +- `src/extension.ts` - this is the main file where you will provide the implementation of your command. + - The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + - We pass the function containing the implementation of the command as the second parameter to `registerCommand`. ## Setup -- install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) +- install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint) ## Get up and running straight away -- Press `F5` to open a new window with your extension loaded. -- Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. -- Set breakpoints in your code inside `src/extension.ts` to debug your extension. -- Find output from your extension in the debug console. +- Press `F5` to open a new window with your extension loaded. +- Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. +- Set breakpoints in your code inside `src/extension.ts` to debug your extension. +- Find output from your extension in the debug console. ## Make changes -- You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -- You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. +- You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +- You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. ## Explore the API -- You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. +- You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. ## Run tests -- Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) -- Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. -- Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` -- See the output of the test result in the Test Results view. -- Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. - - The provided test runner will only consider files matching the name pattern `**.test.ts`. - - You can create folders inside the `test` folder to structure your tests any way you want. +- Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner) +- Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered. +- Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A` +- See the output of the test result in the Test Results view. +- Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder. + - The provided test runner will only consider files matching the name pattern `**.test.ts`. + - You can create folders inside the `test` folder to structure your tests any way you want. ## Go further -- Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). -- [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. -- Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). +- Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). +- [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. +- Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). diff --git a/mcp-servers/mcp-server-vscode/webpack.config.js b/mcp-servers/mcp-server-vscode/webpack.config.js index 7461f92d..0fdbff94 100644 --- a/mcp-servers/mcp-server-vscode/webpack.config.js +++ b/mcp-servers/mcp-server-vscode/webpack.config.js @@ -2,54 +2,54 @@ 'use strict'; -const path = require('path'); - -//@ts-check /** @typedef {import('webpack').Configuration} WebpackConfig **/ +const path = require('path'); + /** @type WebpackConfig */ const extensionConfig = { - target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ - mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') + target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ + mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') - entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ - output: { - // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ - path: path.resolve(__dirname, 'dist'), - filename: 'extension.js', - libraryTarget: 'commonjs2' - }, - externals: { - vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ - // modules added here also need to be added in the .vscodeignore file - }, - resolve: { - // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader - extensions: ['.ts', '.js'] - }, - module: { - rules: [ - { - test: /\.ts$/, - exclude: /node_modules/, - use: [ - { - loader: 'ts-loader' - } - ] - } - ] - }, - devtool: 'nosources-source-map', - infrastructureLogging: { - level: "log", // enables logging required for problem matchers - }, + entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ + output: { + // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, 'dist'), + filename: 'extension.js', + libraryTarget: 'commonjs2', + }, + externals: { + vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ + // modules added here also need to be added in the .vscodeignore file + }, + resolve: { + // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + }, + ], + }, + ], + }, + devtool: 'nosources-source-map', + infrastructureLogging: { + level: 'log', // enables logging required for problem matchers + }, }; + extensionConfig.ignoreWarnings = [ { module: /express\//, - message: /the request of a dependency is an expression/ - } + message: /the request of a dependency is an expression/, + }, ]; -module.exports = [ extensionConfig ]; +module.exports = [extensionConfig]; diff --git a/workbench-app/.vscode/settings.json b/workbench-app/.vscode/settings.json index 00d964c9..04103e34 100644 --- a/workbench-app/.vscode/settings.json +++ b/workbench-app/.vscode/settings.json @@ -179,6 +179,7 @@ "jsonlogger", "jungaretti", "jwks", + "katex", "keyvault", "Langchain", "levelname",