# Directory Structure
```
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .vscode-test.mjs
├── .vscodeignore
├── CHANGELOG.md
├── eslint.config.mjs
├── example.bifrost.config.json
├── example.mdc
├── ExampleCursorRules.md
├── icon.png
├── icon.txt
├── LICENSE
├── package-lock.json
├── package.json
├── placeholder.txt
├── README.md
├── src
│ ├── config.ts
│ ├── debugPanel.ts
│ ├── extension.ts
│ ├── globals.ts
│ ├── helpers.ts
│ ├── images
│ │ ├── commands.png
│ │ ├── cursor.png
│ │ └── debug_panel.png
│ ├── mcpresponses.ts
│ ├── rosyln.ts
│ ├── test
│ │ └── extension.test.ts
│ ├── toolRunner.ts
│ ├── tools.ts
│ └── webview.ts
├── tsconfig.json
├── vsc-extension-quickstart.md
└── webpack.config.js
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | *.vsix
6 | */debug-profile
```
--------------------------------------------------------------------------------
/.vscode-test.mjs:
--------------------------------------------------------------------------------
```
1 | import { defineConfig } from '@vscode/test-cli';
2 |
3 | export default defineConfig({
4 | files: 'out/test/**/*.test.js',
5 | });
6 |
```
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
```
1 | .vscode/**
2 | .vscode-test/**
3 | out/**
4 | node_modules/**
5 | src/**
6 | .gitignore
7 | .yarnrc
8 | webpack.config.js
9 | vsc-extension-quickstart.md
10 | **/tsconfig.json
11 | **/eslint.config.mjs
12 | **/*.map
13 | **/*.ts
14 | **/.vscode-test.*
15 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Bifrost - VSCode Dev Tools MCP Server
2 | <a href="https://marketplace.visualstudio.com/items?itemName=ConnorHallman.bifrost-mcp">
3 | <img src="https://img.shields.io/visual-studio-marketplace/d/ConnorHallman.bifrost-mcp?label=VSCode%20Extension%20Downloads&cacheSeconds=3600"
4 | alt="VSCode Extension Downloads"
5 | width="250">
6 | </a>
7 |
8 | This VS Code extension provides a Model Context Protocol (MCP) server that exposes VSCode's powerful development tools and language features to AI tools. It enables advanced code navigation, analysis, and manipulation capabilities when using AI coding assistants that support the MCP protocol.
9 |
10 | 
11 |
12 | ## Table of Contents
13 | - [Features](#features)
14 | - [Installation/Usage](#usage)
15 | - [Multi-Project Support](#multiple-project-support)
16 | - [Available Tools](#available-tools)
17 | - [Available Commands](#available-commands)
18 | - [Troubleshooting](#troubleshooting)
19 | - [Contributing](#contributing)
20 | - [Debugging](#debugging)
21 | - [License](#license)
22 |
23 | ## Features
24 |
25 | - **Language Server Integration**: Access VSCode's language server capabilities for any supported language
26 | - **Code Navigation**: Find references, definitions, implementations, and more
27 | - **Symbol Search**: Search for symbols across your workspace
28 | - **Code Analysis**: Get semantic tokens, document symbols, and type information
29 | - **Smart Selection**: Use semantic selection ranges for intelligent code selection
30 | - **Code Actions**: Access refactoring suggestions and quick fixes
31 | - **HTTP/SSE Server**: Exposes language features over an MCP-compatible HTTP server
32 | - **AI Assistant Integration**: Ready to work with AI assistants that support the MCP protocol
33 |
34 | ## Usage
35 |
36 | ### Installation
37 |
38 | 1. Install [the extension](https://marketplace.visualstudio.com/items?itemName=ConnorHallman.bifrost-mcp) from the VS Code marketplace
39 | 2. Install any language-specific extensions you need for your development
40 | 3. Open your project in VS Code
41 |
42 | ### Configuration
43 |
44 | The extension will automatically start an MCP server when activated. To configure an AI assistant to use this server:
45 |
46 | 1. The server runs on port 8008 by default (configurable with `bifrost.config.json`)
47 | 2. Configure your MCP-compatible AI assistant to connect to:
48 | - SSE endpoint: `http://localhost:8008/sse`
49 | - Message endpoint: `http://localhost:8008/message`
50 |
51 | ### LLM Rules
52 | I have also provided sample rules that can be used in .cursorrules files for better results.
53 |
54 | [Example Cursor Rules](https://github.com/biegehydra/BifrostMCP/blob/master/ExampleCursorRules.md)
55 |
56 | [Example MDC Rules](https://github.com/biegehydra/BifrostMCP/blob/master/example.mdc)
57 |
58 | ### Cline Installation
59 | - Step 1. Install [Supergateway](https://github.com/supercorp-ai/supergateway)
60 | - Step 2. Add config to cline
61 | - Step 3. It will show up red but seems to work fine
62 |
63 | #### Windows Config
64 | ```json
65 | {
66 | "mcpServers": {
67 | "Bifrost": {
68 | "command": "cmd",
69 | "args": [
70 | "/c",
71 | "npx",
72 | "-y",
73 | "supergateway",
74 | "--sse",
75 | "http://localhost:8008/sse"
76 | ],
77 | "disabled": false,
78 | "autoApprove": [],
79 | "timeout": 600
80 | }
81 | }
82 | }
83 | ```
84 |
85 | #### Mac/Linux Config
86 | ```json
87 | {
88 | "mcpServers": {
89 | "Bifrost": {
90 | "command": "npx",
91 | "args": [
92 | "-y",
93 | "supergateway",
94 | "--sse",
95 | "http://localhost:8008/sse"
96 | ],
97 | "disabled": false,
98 | "autoApprove": [],
99 | "timeout": 600
100 | }
101 | }
102 | }
103 | ```
104 |
105 | ### Roo Code Installation
106 | - Step 1: Add the SSE config to your global or project-based MCP configuration
107 | ```json
108 | {
109 | "mcpServers": {
110 | "Bifrost": {
111 | "url": "http://localhost:8008/sse"
112 | }
113 | }
114 | }
115 | ```
116 |
117 | 
118 |
119 | Follow this video to install and use with cursor
120 |
121 | #### FOR NEW VERSIONS OF CURSOR, USE THIS CODE
122 | ```json
123 | {
124 | "mcpServers": {
125 | "Bifrost": {
126 | "url": "http://localhost:8008/sse"
127 | }
128 | }
129 | }
130 | ```
131 |
132 | ## Multiple Project Support
133 |
134 | When working with multiple projects, each project can have its own dedicated MCP server endpoint and port. This is useful when you have multiple VS Code windows open or are working with multiple projects that need language server capabilities.
135 |
136 | ### Project Configuration
137 |
138 | Create a `bifrost.config.json` file in your project root:
139 |
140 | ```json
141 | {
142 | "projectName": "MyProject",
143 | "description": "Description of your project",
144 | "path": "/my-project",
145 | "port": 5642
146 | }
147 | ```
148 |
149 | The server will use this configuration to:
150 | - Create project-specific endpoints (e.g., `http://localhost:5642/my-project/sse`)
151 | - Provide project information to AI assistants
152 | - Use a dedicated port for each project
153 | - Isolate project services from other running instances
154 |
155 | ### Example Configurations
156 |
157 | 1. Backend API Project:
158 | ```json
159 | {
160 | "projectName": "BackendAPI",
161 | "description": "Node.js REST API with TypeScript",
162 | "path": "/backend-api",
163 | "port": 5643
164 | }
165 | ```
166 |
167 | 2. Frontend Web App:
168 | ```json
169 | {
170 | "projectName": "FrontendApp",
171 | "description": "React frontend application",
172 | "path": "/frontend-app",
173 | "port": 5644
174 | }
175 | ```
176 |
177 | ### Port Configuration
178 |
179 | Each project should specify its own unique port to avoid conflicts when multiple VS Code instances are running:
180 |
181 | - The `port` field in `bifrost.config.json` determines which port the server will use
182 | - If no port is specified, it defaults to 8008 for backwards compatibility
183 | - Choose different ports for different projects to ensure they can run simultaneously
184 | - The server will fail to start if the configured port is already in use, requiring you to either:
185 | - Free up the port
186 | - Change the port in the config
187 | - Close the other VS Code instance using that port
188 |
189 | ### Connecting to Project-Specific Endpoints
190 |
191 | Update your AI assistant configuration to use the project-specific endpoint and port:
192 |
193 | ```json
194 | {
195 | "mcpServers": {
196 | "BackendAPI": {
197 | "url": "http://localhost:5643/backend-api/sse"
198 | },
199 | "FrontendApp": {
200 | "url": "http://localhost:5644/frontend-app/sse"
201 | }
202 | }
203 | }
204 | ```
205 |
206 | ### Backwards Compatibility
207 |
208 | If no `bifrost.config.json` is present, the server will use the default configuration:
209 | - Port: 8008
210 | - SSE endpoint: `http://localhost:8008/sse`
211 | - Message endpoint: `http://localhost:8008/message`
212 |
213 | This maintains compatibility with existing configurations and tools.
214 |
215 | ## Available Tools
216 |
217 | The extension provides access to many VSCode language features including:
218 |
219 | * **find\_usages**: Locate all symbol references.
220 | * **go\_to\_definition**: Jump to symbol definitions instantly.
221 | * **find\_implementations**: Discover implementations of interfaces/abstract methods.
222 | * **get\_hover\_info**: Get rich symbol docs on hover.
223 | * **get\_document\_symbols**: Outline all symbols in a file.
224 | * **get\_completions**: Context-aware auto-completions.
225 | * **get\_signature\_help**: Function parameter hints and overloads.
226 | * **get\_rename\_locations**: Safely get location of places to perform a rename across the project.
227 | * **rename**: Perform rename on a symbol
228 | * **get\_code\_actions**: Quick fixes, refactors, and improvements.
229 | * **get\_semantic\_tokens**: Enhanced highlighting data.
230 | * **get\_call\_hierarchy**: See incoming/outgoing call relationships.
231 | * **get\_type\_hierarchy**: Visualize class and interface inheritance.
232 | * **get\_code\_lens**: Inline insights (references, tests, etc.).
233 | * **get\_selection\_range**: Smart selection expansion for code blocks.
234 | * **get\_type\_definition**: Jump to underlying type definitions.
235 | * **get\_declaration**: Navigate to symbol declarations.
236 | * **get\_document\_highlights**: Highlight all occurrences of a symbol.
237 | * **get\_workspace\_symbols**: Search symbols across your entire workspace.
238 |
239 | ## Requirements
240 |
241 | - Visual Studio Code version 1.93.0 or higher
242 | - Appropriate language extensions for the languages you want to work with (e.g., C# extension for C# files)
243 |
244 | ### Available Commands
245 |
246 | - `Bifrost MCP: Start Server` - Manually start the MCP server on port 8008
247 | - `Bifrost MCP: Start Server on port` - Manually start the MCP server on specified port
248 | - `Bifrost MCP: Stop Server` - Stop the running MCP server
249 | - `Bifrost MCP: Open Debug Panel` - Open the debug panel to test available tools
250 |
251 | 
252 |
253 | ## Star History
254 |
255 | [](https://star-history.com/#biegehydra/BifrostMCP&Date)
256 |
257 | ## Example Tool Usage
258 |
259 | ### Find References
260 | ```json
261 | {
262 | "name": "find_usages",
263 | "arguments": {
264 | "textDocument": {
265 | "uri": "file:///path/to/your/file"
266 | },
267 | "position": {
268 | "line": 10,
269 | "character": 15
270 | },
271 | "context": {
272 | "includeDeclaration": true
273 | }
274 | }
275 | }
276 | ```
277 |
278 | ### Workspace Symbol Search
279 | ```json
280 | {
281 | "name": "get_workspace_symbols",
282 | "arguments": {
283 | "query": "MyClass"
284 | }
285 | }
286 | ```
287 |
288 | ## Troubleshooting
289 |
290 | If you encounter issues:
291 |
292 | 1. Ensure you have the appropriate language extensions installed for your project
293 | 2. Check that your project has loaded correctly in VSCode
294 | 3. Verify that port 8008 is available on your system
295 | 4. Check the VSCode output panel for any error messages
296 |
297 | ## Contributing
298 | Here are [Vscodes commands](https://github.com/microsoft/vscode-docs/blob/main/api/references/commands.md?plain=1) if you want to add additional functionality go ahead. I think we still need rename and a few others.
299 | Please feel free to submit issues or pull requests to the [GitHub repository](https://github.com/biegehydra/csharplangmcpserver).
300 |
301 | `vsce package`
302 |
303 | ## Debugging
304 | Use the `MCP: Open Debug Panel` command
305 | 
306 |
307 | ## License
308 |
309 | This extension is licensed under the APGL-3.0 License.
310 |
```
--------------------------------------------------------------------------------
/src/mcpresponses.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ReferencesAndPreview } from "./rosyln";
2 |
3 | export type FindUsagesResponse = {
4 | references: ReferencesAndPreview[];
5 | };
```
--------------------------------------------------------------------------------
/example.bifrost.config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "projectName": "MyProject",
3 | "description": "A sample project using the Bifrost MCP server",
4 | "path": "/my-project"
5 | }
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher", "ms-vscode.extension-test-runner"]
5 | }
6 |
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # Change Log
2 |
3 | All notable changes to the "csharplangmcpserver" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 |
9 | - Initial release
```
--------------------------------------------------------------------------------
/src/globals.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2 | import type { Server as HttpServer } from 'http';
3 |
4 | export let mcpServer: Server | undefined;
5 | export let httpServer: HttpServer | undefined;
6 |
7 | export const setMcpServer = (server: Server | undefined) => {
8 | mcpServer = server;
9 | }
10 |
11 | export const setHttpServer = (server: HttpServer | undefined) => {
12 | httpServer = server;
13 | }
14 |
```
--------------------------------------------------------------------------------
/src/test/extension.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as assert from 'assert';
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode';
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 |
11 | test('Sample test', () => {
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
14 | });
15 | });
16 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "module": "Node16",
4 | "target": "ES2022",
5 | "lib": [
6 | "ES2022"
7 | ],
8 | "sourceMap": true,
9 | "rootDir": "src",
10 | "strict": true, /* enable all strict type-checking options */
11 | /* Additional Checks */
12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
15 | }
16 | }
17 |
```
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
```json
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
14 |
```
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
```json
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/dist/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}"
19 | }
20 | ]
21 | }
22 |
```
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
```
1 | import typescriptEslint from "@typescript-eslint/eslint-plugin";
2 | import tsParser from "@typescript-eslint/parser";
3 |
4 | export default [{
5 | files: ["**/*.ts"],
6 | }, {
7 | plugins: {
8 | "@typescript-eslint": typescriptEslint,
9 | },
10 |
11 | languageOptions: {
12 | parser: tsParser,
13 | ecmaVersion: 2022,
14 | sourceType: "module",
15 | },
16 |
17 | rules: {
18 | "@typescript-eslint/naming-convention": ["warn", {
19 | selector: "import",
20 | format: ["camelCase", "PascalCase"],
21 | }],
22 |
23 | curly: "warn",
24 | eqeqeq: "warn",
25 | "no-throw-literal": "warn",
26 | semi: "warn",
27 | },
28 | }];
```
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
```json
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": "$ts-webpack-watch",
10 | "isBackground": true,
11 | "presentation": {
12 | "reveal": "never",
13 | "group": "watchers"
14 | },
15 | "group": {
16 | "kind": "build",
17 | "isDefault": true
18 | }
19 | },
20 | {
21 | "type": "npm",
22 | "script": "watch-tests",
23 | "problemMatcher": "$tsc-watch",
24 | "isBackground": true,
25 | "presentation": {
26 | "reveal": "never",
27 | "group": "watchers"
28 | },
29 | "group": "build"
30 | },
31 | {
32 | "label": "tasks: watch-tests",
33 | "dependsOn": [
34 | "npm: watch",
35 | "npm: watch-tests"
36 | ],
37 | "problemMatcher": []
38 | }
39 | ]
40 | }
41 |
```
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
```javascript
1 | //@ts-check
2 |
3 | 'use strict';
4 |
5 | const path = require('path');
6 |
7 | //@ts-check
8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
9 |
10 | /** @type WebpackConfig */
11 | const extensionConfig = {
12 | target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
14 |
15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
16 | output: {
17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
18 | path: path.resolve(__dirname, 'dist'),
19 | filename: 'extension.js',
20 | libraryTarget: 'commonjs2'
21 | },
22 | externals: {
23 | 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/
24 | // modules added here also need to be added in the .vscodeignore file
25 | },
26 | resolve: {
27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
28 | extensions: ['.ts', '.js']
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts$/,
34 | exclude: /node_modules/,
35 | use: [
36 | {
37 | loader: 'ts-loader'
38 | }
39 | ]
40 | }
41 | ]
42 | },
43 | devtool: 'nosources-source-map',
44 | infrastructureLogging: {
45 | level: "log", // enables logging required for problem matchers
46 | },
47 | };
48 | module.exports = [ extensionConfig ];
```
--------------------------------------------------------------------------------
/src/rosyln.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Matches the C# `ReferenceParams` class, which extends TextDocumentPositionParams
3 | * and adds:
4 | * - context (ReferenceContext)
5 | * - workDoneToken? (IProgress<WorkDoneProgress>)
6 | * - partialResultToken? (IProgress<Location[]>)
7 | */
8 | export interface ReferenceParams extends TextDocumentPositionParams {
9 | /**
10 | * Matches C# `ReferenceParams.Context`.
11 | */
12 | context: ReferenceContext;
13 | }
14 |
15 | /**
16 | * Matches the C# `ReferenceContext` class
17 | */
18 | export interface ReferenceContext {
19 | /**
20 | * Include the declaration of the current symbol.
21 | */
22 | includeDeclaration: boolean;
23 | }
24 |
25 | /**
26 | * Matches the C# `TextDocumentPositionParams` base class,
27 | * which has a TextDocumentIdentifier and a Position.
28 | */
29 | export interface TextDocumentPositionParams {
30 | /**
31 | * Matches C# `TextDocumentPositionParams.TextDocument`
32 | */
33 | textDocument: TextDocumentIdentifier;
34 |
35 | /**
36 | * Matches C# `TextDocumentPositionParams.Position`
37 | */
38 | position: Position;
39 | }
40 |
41 | /**
42 | * Matches the C# `TextDocumentIdentifier` with its `uri` property.
43 | */
44 | export interface TextDocumentIdentifier {
45 | /**
46 | * The URI of the text document (C# `Uri Uri`).
47 | */
48 | uri: string;
49 | }
50 |
51 | /**
52 | * Matches C# `Position`, which contains a `Line` and `Character`.
53 | */
54 | export interface Position {
55 | line: number;
56 | character: number;
57 | }
58 |
59 | export interface Range {
60 | start: Position;
61 | end: Position;
62 | }
63 |
64 | export interface ReferencesResponse {
65 | uri: string;
66 | range: Range;
67 | }
68 |
69 | export interface ReferencesAndPreview extends ReferencesResponse {
70 | preview: string;
71 | }
72 |
73 | export interface RenameEdit {
74 | uri: string;
75 | edits: {
76 | range: {
77 | start: { line: number; character: number; };
78 | end: { line: number; character: number; };
79 | };
80 | newText: string;
81 | }[];
82 | }
```
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 |
4 | export interface BifrostConfig {
5 | projectName: string;
6 | description: string;
7 | path: string;
8 | port: number;
9 | }
10 |
11 | export const DEFAULT_CONFIG: BifrostConfig = {
12 | projectName: "language-tools",
13 | description: "Language tools and code analysis",
14 | path: "", // Empty path for backwards compatibility
15 | port: 8008 // Default port for backwards compatibility
16 | };
17 |
18 | export async function findBifrostConfig(workspaceFolder: vscode.WorkspaceFolder): Promise<BifrostConfig> {
19 | const configPath = path.join(workspaceFolder.uri.fsPath, 'bifrost.config.json');
20 |
21 | try {
22 | // Check if config file exists and read it
23 | const configFile = await vscode.workspace.fs.readFile(vscode.Uri.file(configPath));
24 | const configContent = Buffer.from(configFile).toString('utf8');
25 | const config: BifrostConfig = JSON.parse(configContent);
26 |
27 | // Validate config
28 | if (!config.projectName || !config.description || config.path === undefined) {
29 | throw new Error('Invalid bifrost.config.json: missing required fields');
30 | }
31 |
32 | // Use default port if not specified
33 | if (config.port === undefined) {
34 | config.port = DEFAULT_CONFIG.port;
35 | }
36 |
37 | return {
38 | projectName: config.projectName,
39 | description: config.description,
40 | path: config.path,
41 | port: config.port
42 | };
43 | } catch (error) {
44 | console.log(`No valid bifrost.config.json found in ${workspaceFolder.name}, using default config`);
45 | return DEFAULT_CONFIG;
46 | }
47 | }
48 |
49 | export function getProjectBasePath(config: BifrostConfig): string {
50 | // For backwards compatibility, if path is empty, return empty string (root path)
51 | if (!config.path) {
52 | return '';
53 | }
54 | if (!config.path.startsWith('/')) {
55 | config.path = '/' + config.path;
56 | }
57 | return `${config.path}`;
58 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "bifrost-mcp",
3 | "displayName": "Bifrost - VSCode Dev Tools MCP Server",
4 | "description": "An MCP server that exposes features of VSCode dev tools to the MCP clients",
5 | "version": "0.0.14",
6 | "publisher": "ConnorHallman",
7 | "icon": "icon.png",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/biegehydra/csharplangmcpserver"
11 | },
12 | "engines": {
13 | "vscode": "^1.93.0"
14 | },
15 | "categories": [
16 | "Other",
17 | "Programming Languages",
18 | "Language Packs"
19 | ],
20 | "keywords": [
21 | "C#",
22 | "MCP",
23 | "dotnet",
24 | "AI",
25 | "find references"
26 | ],
27 | "activationEvents": [
28 | "onStartupFinished"
29 | ],
30 | "main": "./dist/extension.js",
31 | "contributes": {
32 | "commands": [
33 | {
34 | "command": "bifrost-mcp.startServer",
35 | "title": "Bifrost MCP: Start Server"
36 | },
37 | {
38 | "command": "bifrost-mcp.startServerOnPort",
39 | "title": "Bifrost MCP: Start Server on Port"
40 | },
41 | {
42 | "command": "bifrost-mcp.stopServer",
43 | "title": "Bifrost MCP: Stop Server"
44 | },
45 | {
46 | "command": "bifrost-mcp.openDebugPanel",
47 | "title": "Bifrost MCP: Open Debug Panel"
48 | }
49 | ]
50 | },
51 | "scripts": {
52 | "vscode:prepublish": "npm run package",
53 | "compile": "webpack",
54 | "watch": "webpack --watch",
55 | "package": "webpack --mode production --devtool hidden-source-map",
56 | "compile-tests": "tsc -p . --outDir out",
57 | "watch-tests": "tsc -p . -w --outDir out",
58 | "pretest": "npm run compile-tests && npm run compile && npm run lint",
59 | "lint": "eslint src",
60 | "test": "vscode-test"
61 | },
62 | "devDependencies": {
63 | "@types/mocha": "^10.0.10",
64 | "@types/node": "20.x",
65 | "@types/vscode": "^1.93.0",
66 | "@typescript-eslint/eslint-plugin": "^8.25.0",
67 | "@typescript-eslint/parser": "^8.25.0",
68 | "@vscode/test-cli": "^0.0.10",
69 | "@vscode/test-electron": "^2.4.1",
70 | "eslint": "^9.21.0",
71 | "ts-loader": "^9.5.2",
72 | "typescript": "^5.7.3",
73 | "vscode-languageclient": "^9.0.1",
74 | "webpack": "^5.98.0",
75 | "webpack-cli": "^6.0.1"
76 | },
77 | "dependencies": {
78 | "@modelcontextprotocol/sdk": "^1.6.1",
79 | "@types/cors": "^2.8.17",
80 | "@types/express": "^5.0.0",
81 | "cors": "^2.8.5",
82 | "express": "^5.0.1"
83 | }
84 | }
85 |
```
--------------------------------------------------------------------------------
/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
```markdown
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your extension and command.
7 | * 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.
8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command.
9 | * 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`.
10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
11 |
12 | ## Setup
13 |
14 | * install the recommended extensions (amodio.tsl-problem-matcher, ms-vscode.extension-test-runner, and dbaeumer.vscode-eslint)
15 |
16 |
17 | ## Get up and running straight away
18 |
19 | * Press `F5` to open a new window with your extension loaded.
20 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
21 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension.
22 | * Find output from your extension in the debug console.
23 |
24 | ## Make changes
25 |
26 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
27 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
28 |
29 |
30 | ## Explore the API
31 |
32 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
33 |
34 | ## Run tests
35 |
36 | * Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
37 | * Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
38 | * Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
39 | * See the output of the test result in the Test Results view.
40 | * Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
41 | * The provided test runner will only consider files matching the name pattern `**.test.ts`.
42 | * You can create folders inside the `test` folder to structure your tests any way you want.
43 |
44 | ## Go further
45 |
46 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
47 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
48 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
49 |
```
--------------------------------------------------------------------------------
/ExampleCursorRules.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cursor Agent Rules for Code Modification
2 |
3 | ## 1. Pre-Edit Analysis Requirements
4 | BEFORE making any code changes, MUST:
5 | - Use `mcp__get_document_symbols` on the target file to understand its structure
6 | - Use `mcp__find_usages` on any symbol being modified to identify all affected locations
7 | - Use `mcp__get_type_definition` and/or `mcp__go_to_definition` for any types or symbols being modified
8 | - Use `mcp__get_hover_info` to verify function signatures and type information
9 |
10 | ## 2. Impact Analysis Rules
11 | BEFORE proceeding with changes:
12 | 1. If `mcp__find_usages` reveals usage in multiple files:
13 | - Must analyze each usage context
14 | - Must verify type compatibility across all uses
15 | - Must plan changes for all affected locations
16 |
17 | 2. If modifying interfaces or types:
18 | - Must use `mcp__find_implementations` to locate all implementations
19 | - Must ensure changes won't break implementing classes
20 | - Must verify backward compatibility or plan updates for all implementations
21 |
22 | ## 3. Type Safety Rules
23 | MUST maintain type safety by:
24 | 1. Using `mcp__get_type_definition` for:
25 | - All modified parameters
26 | - Return types
27 | - Interface members
28 | - Generic constraints
29 |
30 | 2. Using `mcp__get_hover_info` to verify:
31 | - Function signatures
32 | - Type constraints
33 | - Optional vs required properties
34 |
35 | ## 4. Code Modification Sequence
36 | When making changes:
37 | 1. First gather context:
38 | ```typescript
39 | // Example sequence
40 | await mcp__get_document_symbols(file)
41 | await mcp__find_usages(symbol)
42 | await mcp__get_type_definition(symbol)
43 | await mcp__get_hover_info(symbol)
44 | ```
45 |
46 | 2. Then analyze impact:
47 | ```typescript
48 | // For each usage found
49 | await mcp__get_hover_info(usage)
50 | await mcp__get_type_definition(relatedTypes)
51 | ```
52 |
53 | 3. Only then use `edit_file`
54 |
55 | ## 5. Post-Edit Verification
56 | After making changes:
57 | 1. Use `mcp__get_document_symbols` to verify file structure remains valid
58 | 2. Use `mcp__find_usages` to verify all usages are still compatible
59 | 3. Use `mcp__get_hover_info` to verify new type signatures
60 |
61 | ## 6. Special Cases
62 |
63 | ### When Modifying React Components:
64 | 1. Must use `mcp__find_usages` to:
65 | - Find all component instances
66 | - Verify prop usage
67 | - Check for defaultProps and propTypes
68 |
69 | 2. Must use `mcp__get_type_definition` for:
70 | - Prop interfaces
71 | - State types
72 | - Context types
73 |
74 | ### When Modifying APIs/Functions:
75 | 1. Must use `mcp__get_call_hierarchy` to:
76 | - Understand the call chain
77 | - Identify dependent functions
78 | - Verify changes won't break callers
79 |
80 | ### When Modifying Types/Interfaces:
81 | 1. Must use `mcp__find_implementations` to:
82 | - Locate all implementing classes
83 | - Verify compatibility
84 | - Plan updates if needed
85 |
86 | ## 7. Error Prevention Rules
87 |
88 | 1. NEVER modify a symbol without first:
89 | ```typescript
90 | await mcp__find_usages(symbol)
91 | await mcp__get_type_definition(symbol)
92 | ```
93 |
94 | 2. NEVER modify a type without:
95 | ```typescript
96 | await mcp__find_implementations(type)
97 | await mcp__get_hover_info(type)
98 | ```
99 |
100 | 3. NEVER modify a function signature without:
101 | ```typescript
102 | await mcp__get_call_hierarchy(function)
103 | await mcp__find_usages(function)
104 | ```
105 |
106 | ## 8. Documentation Requirements
107 |
108 | When explaining changes, must reference:
109 | 1. What tools were used to analyze the code
110 | 2. What usages were found
111 | 3. What type information was verified
112 | 4. What impact analysis was performed
113 |
114 | Example:
115 | ```markdown
116 | I analyzed the code using:
117 | 1. mcp__find_usages to locate all 5 usages of handleSubmit
118 | 2. mcp__get_type_definition to verify the function signature
119 | 3. mcp__get_hover_info to check parameter types
120 | 4. mcp__get_document_symbols to understand the component structure
121 | ```
122 |
123 | ## 9. Change Abort Conditions
124 |
125 | Must ABORT changes if:
126 | 1. `mcp__find_usages` reveals unexpected usages
127 | 2. `mcp__get_type_definition` shows incompatible types
128 | 3. `mcp__find_implementations` shows breaking changes
129 | 4. Unable to verify full impact using available tools
130 |
131 | ## 10. Tool Priority Order
132 |
133 | When analyzing code, use tools in this order:
134 | 1. `mcp__get_document_symbols` (understand structure)
135 | 2. `mcp__find_usages` (understand impact)
136 | 3. `mcp__get_type_definition` (verify types)
137 | 4. `mcp__get_hover_info` (verify signatures)
138 | 5. Additional tools as needed
139 |
```
--------------------------------------------------------------------------------
/src/helpers.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | export interface DocumentSymbolResult {
3 | name: string;
4 | detail: string;
5 | kind: string;
6 | range: {
7 | start: { line: number; character: number };
8 | end: { line: number; character: number };
9 | };
10 | selectionRange: {
11 | start: { line: number; character: number };
12 | end: { line: number; character: number };
13 | };
14 | children: DocumentSymbolResult[];
15 | }
16 |
17 | export function convertSymbol(symbol: vscode.DocumentSymbol): DocumentSymbolResult {
18 | return {
19 | name: symbol.name,
20 | detail: symbol.detail,
21 | kind: getSymbolKindString(symbol.kind),
22 | range: {
23 | start: {
24 | line: symbol.range.start.line,
25 | character: symbol.range.start.character
26 | },
27 | end: {
28 | line: symbol.range.end.line,
29 | character: symbol.range.end.character
30 | }
31 | },
32 | selectionRange: {
33 | start: {
34 | line: symbol.selectionRange.start.line,
35 | character: symbol.selectionRange.start.character
36 | },
37 | end: {
38 | line: symbol.selectionRange.end.line,
39 | character: symbol.selectionRange.end.character
40 | }
41 | },
42 | children: symbol.children.map(convertSymbol)
43 | };
44 | }
45 |
46 | export function getSymbolKindString(kind: vscode.SymbolKind): string {
47 | return vscode.SymbolKind[kind];
48 | }
49 |
50 | export async function getPreview(uri: vscode.Uri, line: number | undefined): Promise<string> {
51 | if (line === null || line === undefined) {
52 | return "";
53 | }
54 | try{
55 | const document = await vscode.workspace.openTextDocument(uri);
56 | const lineText = document.lineAt(line).text.trim();
57 | return lineText;
58 | } catch (error) {
59 | console.error(`Error getting preview for ${uri}: ${error}`);
60 | return "Failed to get preview";
61 | }
62 | }
63 |
64 | export function createVscodePosition(line: number, character: number): vscode.Position | undefined {
65 | if (line === null || line === undefined) {
66 | return undefined;
67 | }
68 | if (character === null || character === undefined) {
69 | return undefined;
70 | }
71 | return new vscode.Position(
72 | Math.max(line, 0),
73 | Math.max(character, 0)
74 | );
75 | }
76 |
77 | export async function asyncMap<T, R>(array: T[], asyncCallback: (item: T) => Promise<R>): Promise<R[]> {
78 | return Promise.all(array.map(asyncCallback));
79 | }
80 |
81 | export function convertSemanticTokens(semanticTokens: vscode.SemanticTokens, document: vscode.TextDocument): any {
82 | const tokens: any[] = [];
83 | let prevLine = 0;
84 | let prevChar = 0;
85 |
86 | // Token types and modifiers from VS Code
87 | const tokenTypes = [
88 | 'namespace', 'type', 'class', 'enum', 'interface',
89 | 'struct', 'typeParameter', 'parameter', 'variable', 'property',
90 | 'enumMember', 'event', 'function', 'method', 'macro',
91 | 'keyword', 'modifier', 'comment', 'string', 'number',
92 | 'regexp', 'operator', 'decorator'
93 | ];
94 |
95 | const tokenModifiers = [
96 | 'declaration', 'definition', 'readonly', 'static',
97 | 'deprecated', 'abstract', 'async', 'modification',
98 | 'documentation', 'defaultLibrary'
99 | ];
100 |
101 | // Process tokens in groups of 5 (format: deltaLine, deltaStartChar, length, tokenType, tokenModifiers)
102 | for (let i = 0; i < semanticTokens.data.length; i += 5) {
103 | const deltaLine = semanticTokens.data[i];
104 | const deltaStartChar = semanticTokens.data[i + 1];
105 | const length = semanticTokens.data[i + 2];
106 | const tokenType = tokenTypes[semanticTokens.data[i + 3]] || 'unknown';
107 | const tokenModifiersBitset = semanticTokens.data[i + 4];
108 |
109 | // Calculate absolute position
110 | const line = prevLine + deltaLine;
111 | const startChar = deltaLine === 0 ? prevChar + deltaStartChar : deltaStartChar;
112 |
113 | // Get the actual text content
114 | const tokenText = document.lineAt(line).text.substr(startChar, length);
115 |
116 | // Convert token modifiers bitset to array of strings
117 | const modifiers = tokenModifiers.filter((_, index) => tokenModifiersBitset & (1 << index));
118 |
119 | tokens.push({
120 | line,
121 | startCharacter: startChar,
122 | length,
123 | tokenType,
124 | modifiers,
125 | text: tokenText
126 | });
127 |
128 | prevLine = line;
129 | prevChar = startChar;
130 | }
131 |
132 | return tokens;
133 | }
134 |
135 | export const transformSingleLocation = async (loc: vscode.Location | vscode.LocationLink): Promise<any> => {
136 | const uri = 'targetUri' in loc ? loc.targetUri : loc.uri;
137 | const range = 'targetRange' in loc ? loc.targetRange : loc.range;
138 |
139 | return {
140 | uri: uri.toString(),
141 | range: range ? {
142 | start: {
143 | line: range.start.line,
144 | character: range.start.character
145 | },
146 | end: {
147 | line: range.end.line,
148 | character: range.end.character
149 | }
150 | } : undefined,
151 | preview: await getPreview(uri, range?.start.line)
152 | };
153 | };
154 |
155 | export const transformLocations = async (locations: (vscode.Location | vscode.LocationLink)[]): Promise<any[]> => {
156 | if (!locations) {
157 | return [];
158 | }
159 | return asyncMap(locations, transformSingleLocation);
160 | };
161 |
162 |
```
--------------------------------------------------------------------------------
/src/debugPanel.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | import { webviewHtml } from './webview';
3 | import { mcpServer } from './globals';
4 | import { runTool } from './toolRunner';
5 | let debugPanel: vscode.WebviewPanel | undefined;
6 | export function createDebugPanel(context: vscode.ExtensionContext) {
7 | if (debugPanel) {
8 | debugPanel.reveal();
9 | return;
10 | }
11 |
12 | debugPanel = vscode.window.createWebviewPanel(
13 | 'mcpDebug',
14 | 'MCP Debug Panel',
15 | vscode.ViewColumn.Two,
16 | {
17 | enableScripts: true
18 | }
19 | );
20 |
21 | // Get workspace files for autocomplete
22 | async function getWorkspaceFiles(): Promise<string[]> {
23 | const workspaceFolders = vscode.workspace.workspaceFolders;
24 | if (!workspaceFolders) return [];
25 |
26 | const files: string[] = [];
27 | for (const folder of workspaceFolders) {
28 | const pattern = new vscode.RelativePattern(folder, '**/*');
29 | const uris = await vscode.workspace.findFiles(pattern);
30 | files.push(...uris.map(uri => uri.toString()));
31 | }
32 | return files;
33 | }
34 |
35 | // Send initial file list and set up file watcher
36 | getWorkspaceFiles().then(files => {
37 | debugPanel?.webview.postMessage({ type: 'files', files });
38 | });
39 |
40 | const fileWatcher = vscode.workspace.createFileSystemWatcher('**/*');
41 | fileWatcher.onDidCreate(() => {
42 | getWorkspaceFiles().then(files => {
43 | debugPanel?.webview.postMessage({ type: 'files', files });
44 | });
45 | });
46 | fileWatcher.onDidDelete(() => {
47 | getWorkspaceFiles().then(files => {
48 | debugPanel?.webview.postMessage({ type: 'files', files });
49 | });
50 | });
51 |
52 | debugPanel.onDidDispose(() => {
53 | fileWatcher.dispose();
54 | debugPanel = undefined;
55 | });
56 | debugPanel.webview.html = webviewHtml;
57 |
58 | // Handle messages from the webview
59 | debugPanel.webview.onDidReceiveMessage(async message => {
60 | if (message.command === 'getCurrentFile') {
61 | const editor = vscode.window.activeTextEditor;
62 | if (editor) {
63 | const uri = editor.document.uri;
64 | debugPanel?.webview.postMessage({
65 | type: 'currentFile',
66 | tool: message.tool,
67 | uri: uri.toString()
68 | });
69 | } else {
70 | debugPanel?.webview.postMessage({
71 | type: 'currentFile',
72 | tool: message.tool,
73 | error: 'No active editor found'
74 | });
75 | vscode.window.showInformationMessage('Please open a file in the editor to use this feature');
76 | }
77 | } else if (message.command === 'execute' && mcpServer) {
78 | try {
79 | // Create a request handler function that matches our server's handlers
80 | const handleRequest = async (request: { params: { name: string; arguments: any } }) => {
81 | try {
82 | const { name, arguments: args } = request.params;
83 | let result: any;
84 |
85 | // Verify file exists for commands that require it
86 | if (args && typeof args === 'object' && 'textDocument' in args &&
87 | args.textDocument && typeof args.textDocument === 'object' &&
88 | 'uri' in args.textDocument && typeof args.textDocument.uri === 'string') {
89 | const uri = vscode.Uri.parse(args.textDocument.uri);
90 | try {
91 | await vscode.workspace.fs.stat(uri);
92 | } catch (error) {
93 | return {
94 | content: [{
95 | type: "text",
96 | text: `Error: File not found - ${uri.fsPath}`
97 | }],
98 | isError: true
99 | };
100 | }
101 | }
102 |
103 | result = await runTool(name, args);
104 |
105 | return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
106 | } catch (error) {
107 | const errorMessage = error instanceof Error ? error.message : String(error);
108 | return {
109 | content: [{ type: "text", text: `Error: ${errorMessage}` }],
110 | isError: true,
111 | };
112 | }
113 | };
114 |
115 | const result = await handleRequest({
116 | params: {
117 | name: message.tool,
118 | arguments: message.params
119 | }
120 | });
121 |
122 | debugPanel?.webview.postMessage({
123 | type: 'result',
124 | tool: message.tool,
125 | result: result
126 | });
127 | } catch (error) {
128 | debugPanel?.webview.postMessage({
129 | type: 'result',
130 | tool: message.tool,
131 | result: { error: String(error) }
132 | });
133 | }
134 | }
135 | });
136 | }
137 |
```
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
4 | import {
5 | CallToolRequestSchema,
6 | ListResourcesRequestSchema,
7 | ListResourceTemplatesRequestSchema,
8 | ListToolsRequestSchema
9 | } from '@modelcontextprotocol/sdk/types.js';
10 | import express from 'express';
11 | import cors from 'cors';
12 | import type { Server as HttpServer } from 'http';
13 | import { Request, Response } from 'express';
14 | import { mcpTools } from './tools';
15 | import { createDebugPanel } from './debugPanel';
16 | import { mcpServer, httpServer, setMcpServer, setHttpServer } from './globals';
17 | import { runTool } from './toolRunner';
18 | import { findBifrostConfig, BifrostConfig, getProjectBasePath } from './config';
19 |
20 | export async function activate(context: vscode.ExtensionContext) {
21 | let currentConfig: BifrostConfig | null = null;
22 |
23 | // Handle workspace folder changes
24 | context.subscriptions.push(
25 | vscode.workspace.onDidChangeWorkspaceFolders(async () => {
26 | await restartServerWithConfig();
27 | })
28 | );
29 |
30 | // Initial server start with config
31 | await restartServerWithConfig();
32 |
33 | // Register debug panel command
34 | context.subscriptions.push(
35 | vscode.commands.registerCommand('bifrost-mcp.openDebugPanel', () => {
36 | createDebugPanel(context);
37 | })
38 | );
39 |
40 | // Register commands
41 | context.subscriptions.push(
42 | vscode.commands.registerCommand('bifrost-mcp.startServer', async () => {
43 | try {
44 | if (httpServer) {
45 | vscode.window.showInformationMessage(`MCP server is already running for project ${currentConfig?.projectName || 'unknown'}`);
46 | return;
47 | }
48 | await restartServerWithConfig();
49 | } catch (error) {
50 | const errorMsg = error instanceof Error ? error.message : String(error);
51 | vscode.window.showErrorMessage(`Failed to start MCP server: ${errorMsg}`);
52 | }
53 | }),
54 | vscode.commands.registerCommand('bifrost-mcp.stopServer', async () => {
55 | if (!httpServer && !mcpServer) {
56 | vscode.window.showInformationMessage('No MCP server is currently running');
57 | return;
58 | }
59 |
60 | if (mcpServer) {
61 | mcpServer.close();
62 | setMcpServer(undefined);
63 | }
64 |
65 | if (httpServer) {
66 | httpServer.close();
67 | setHttpServer(undefined);
68 | }
69 |
70 | vscode.window.showInformationMessage('MCP server stopped');
71 | })
72 | );
73 |
74 | async function restartServerWithConfig() {
75 | // Stop existing server if running
76 | if (mcpServer) {
77 | mcpServer.close();
78 | setMcpServer(undefined);
79 | }
80 | if (httpServer) {
81 | httpServer.close();
82 | setHttpServer(undefined);
83 | }
84 |
85 | // Get workspace folder
86 | const workspaceFolders = vscode.workspace.workspaceFolders;
87 | if (!workspaceFolders || workspaceFolders.length === 0) {
88 | console.log('No workspace folder found');
89 | return;
90 | }
91 |
92 | // Find config in current workspace - will return DEFAULT_CONFIG if none found
93 | const config = await findBifrostConfig(workspaceFolders[0]);
94 | currentConfig = config!; // We know this is never null since findBifrostConfig always returns DEFAULT_CONFIG
95 | await startMcpServer(config!);
96 | }
97 |
98 | async function startMcpServer(config: BifrostConfig): Promise<{ mcpServer: Server, httpServer: HttpServer, port: number }> {
99 | // Create an MCP Server with project-specific info
100 | setMcpServer(new Server(
101 | {
102 | name: config.projectName,
103 | version: "0.1.0",
104 | description: config.description
105 | },
106 | {
107 | capabilities: {
108 | tools: {},
109 | resources: {},
110 | }
111 | }
112 | ));
113 |
114 | // Add tools handlers
115 | mcpServer!.setRequestHandler(ListToolsRequestSchema, async () => ({
116 | tools: mcpTools
117 | }));
118 |
119 | mcpServer!.setRequestHandler(ListResourcesRequestSchema, async () => ({
120 | resources: []
121 | }));
122 |
123 | mcpServer!.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
124 | templates: []
125 | }));
126 |
127 | // Add call tool handler
128 | mcpServer!.setRequestHandler(CallToolRequestSchema, async (request) => {
129 | try {
130 | const { name, arguments: args } = request.params;
131 | let result: any;
132 |
133 | // Verify file exists for commands that require it
134 | if (args && typeof args === 'object' && 'textDocument' in args &&
135 | args.textDocument && typeof args.textDocument === 'object' &&
136 | 'uri' in args.textDocument && typeof args.textDocument.uri === 'string') {
137 | const uri = vscode.Uri.parse(args.textDocument.uri);
138 | try {
139 | await vscode.workspace.fs.stat(uri);
140 | } catch (error) {
141 | return {
142 | content: [{
143 | type: "text",
144 | text: `Error: File not found - ${uri.fsPath}`
145 | }],
146 | isError: true
147 | };
148 | }
149 | }
150 |
151 | result = await runTool(name, args);
152 |
153 | return { content: [{ type: "text", text: JSON.stringify(result) }] };
154 | } catch (error) {
155 | const errorMessage = error instanceof Error ? error.message : String(error);
156 | return {
157 | content: [{ type: "text", text: `Error: ${errorMessage}` }],
158 | isError: true,
159 | };
160 | }
161 | });
162 |
163 | // Set up Express app
164 | const app = express();
165 | app.use(cors());
166 | app.use(express.json());
167 |
168 | // Track active transports by session ID
169 | const transports: { [sessionId: string]: SSEServerTransport } = {};
170 |
171 | const basePath = getProjectBasePath(config);
172 |
173 | // Create project-specific SSE endpoint
174 | app.get(`${basePath}/sse`, async (req: Request, res: Response) => {
175 | console.log(`New SSE connection attempt for project ${config.projectName}`);
176 |
177 | req.socket.setTimeout(0);
178 | req.socket.setNoDelay(true);
179 | req.socket.setKeepAlive(true);
180 |
181 | try {
182 | // Create transport with project-specific message endpoint path
183 | const transport = new SSEServerTransport(`${basePath}/message`, res);
184 | const sessionId = transport.sessionId;
185 | transports[sessionId] = transport;
186 |
187 | const keepAliveInterval = setInterval(() => {
188 | if (res.writable) {
189 | res.write(': keepalive\n\n');
190 | }
191 | }, 30000);
192 |
193 | if (mcpServer) {
194 | await mcpServer.connect(transport);
195 | console.log(`Server connected to SSE transport with session ID: ${sessionId} for project ${config.projectName}`);
196 |
197 | req.on('close', () => {
198 | console.log(`SSE connection closed for session ${sessionId}`);
199 | clearInterval(keepAliveInterval);
200 | delete transports[sessionId];
201 | transport.close().catch(err => {
202 | console.error('Error closing transport:', err);
203 | });
204 | });
205 | } else {
206 | console.error('MCP Server not initialized');
207 | res.status(500).end();
208 | return;
209 | }
210 | } catch (error) {
211 | console.error('Error in SSE connection:', error);
212 | res.status(500).end();
213 | }
214 | });
215 |
216 | // Create project-specific message endpoint
217 | app.post(`${basePath}/message`, async (req: Request, res: Response) => {
218 | const sessionId = req.query.sessionId as string;
219 | console.log(`Received message for session ${sessionId} in project ${config.projectName}:`, req.body?.method);
220 |
221 | const transport = transports[sessionId];
222 | if (!transport) {
223 | console.error(`No transport found for session ${sessionId}`);
224 | res.status(400).json({
225 | jsonrpc: "2.0",
226 | id: req.body?.id,
227 | error: {
228 | code: -32000,
229 | message: "No active session found"
230 | }
231 | });
232 | return;
233 | }
234 |
235 | try {
236 | await transport.handlePostMessage(req, res, req.body);
237 | console.log('Message handled successfully');
238 | } catch (error) {
239 | console.error('Error handling message:', error);
240 | res.status(500).json({
241 | jsonrpc: "2.0",
242 | id: req.body?.id,
243 | error: {
244 | code: -32000,
245 | message: String(error)
246 | }
247 | });
248 | }
249 | });
250 |
251 | // Add project-specific health check endpoint
252 | app.get(`${basePath}/health`, (req: Request, res: Response) => {
253 | res.status(200).json({
254 | status: 'ok',
255 | project: config.projectName,
256 | description: config.description
257 | });
258 | });
259 |
260 | try {
261 | const serv = app.listen(config.port);
262 | setHttpServer(serv);
263 | vscode.window.showInformationMessage(`MCP server listening on http://localhost:${config.port}${basePath}`);
264 | console.log(`MCP Server for project ${config.projectName} listening on http://localhost:${config.port}${basePath}`);
265 | return {
266 | mcpServer: mcpServer!,
267 | httpServer: httpServer!,
268 | port: config.port
269 | };
270 | } catch (error) {
271 | const errorMsg = error instanceof Error ? error.message : String(error);
272 | vscode.window.showErrorMessage(`Failed to start server on configured port ${config.port}${basePath}. Please check if the port is available or configure a different port in bifrost.config.json. Error: ${errorMsg}`);
273 | throw new Error(`Failed to start server on configured port ${config.port}. Please check if the port is available or configure a different port in bifrost.config.json. Error: ${errorMsg}`);
274 | }
275 | }
276 | }
277 |
278 | export function deactivate() {
279 | if (mcpServer) {
280 | mcpServer.close();
281 | }
282 | if (httpServer) {
283 | httpServer.close();
284 | }
285 | }
```
--------------------------------------------------------------------------------
/src/webview.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { toolsDescriptions } from './tools';
2 | const onlyUriTools = ['get_semantic_tokens', 'get_document_symbols', 'get_code_lens', 'get_workspace_symbols'];
3 | const noUriTools = ['get_workspace_symbols'];
4 |
5 | export const webviewHtml = `
6 | <!DOCTYPE html>
7 | <html>
8 | <head>
9 | <style>
10 | body {
11 | padding: 20px;
12 | font-family: var(--vscode-font-family);
13 | color: var(--vscode-foreground);
14 | }
15 | .tool-section {
16 | margin-bottom: 20px;
17 | padding: 10px;
18 | border: 1px solid var(--vscode-panel-border);
19 | border-radius: 4px;
20 | }
21 | .tool-title {
22 | font-weight: bold;
23 | margin-bottom: 10px;
24 | }
25 | input, button {
26 | margin: 5px 0;
27 | padding: 5px;
28 | background: var(--vscode-input-background);
29 | color: var(--vscode-input-foreground);
30 | border: 1px solid var(--vscode-input-border);
31 | }
32 | button {
33 | cursor: pointer;
34 | background: var(--vscode-button-background);
35 | color: var(--vscode-button-foreground);
36 | border: none;
37 | padding: 8px 12px;
38 | border-radius: 2px;
39 | }
40 | button:hover {
41 | background: var(--vscode-button-hoverBackground);
42 | }
43 | pre {
44 | background: var(--vscode-textCodeBlock-background);
45 | padding: 10px;
46 | overflow-x: auto;
47 | margin-top: 10px;
48 | color: var(--vscode-foreground);
49 | }
50 | .error-message {
51 | color: var(--vscode-errorForeground);
52 | }
53 | .success-message {
54 | color: var(--vscode-terminal-ansiGreen);
55 | }
56 | .autocomplete-container {
57 | position: relative;
58 | width: 100%;
59 | }
60 | .autocomplete-list {
61 | position: absolute;
62 | top: 100%;
63 | left: 0;
64 | right: 0;
65 | max-height: 200px;
66 | overflow-y: auto;
67 | background: var(--vscode-input-background);
68 | border: 1px solid var(--vscode-input-border);
69 | z-index: 1000;
70 | display: none;
71 | }
72 | .autocomplete-item {
73 | padding: 5px 10px;
74 | cursor: pointer;
75 | }
76 | .autocomplete-item:hover {
77 | background: var(--vscode-list-hoverBackground);
78 | }
79 | .autocomplete-item.selected {
80 | background: var(--vscode-list-activeSelectionBackground);
81 | color: var(--vscode-list-activeSelectionForeground);
82 | }
83 | .current-file-button {
84 | margin-left: 5px;
85 | }
86 | .tool-inputs {
87 | margin: 10px 0;
88 | }
89 | </style>
90 | </head>
91 | <body>
92 | ${toolsDescriptions.map(tool => `
93 | <div class="tool-section">
94 | <div class="tool-title">${tool.name}</div>
95 | <div>${tool.description}</div>
96 | ${!noUriTools.includes(tool.name) ? `
97 | <div class="autocomplete-container">
98 | <div style="display: flex; align-items: center;">
99 | <input type="text" id="uri-${tool.name}" class="file-input" placeholder="Start typing to search files..." style="flex: 1;">
100 | <button class="current-file-button" onclick="useCurrentFile('${tool.name}')">Use Current File</button>
101 | </div>
102 | <div id="autocomplete-${tool.name}" class="autocomplete-list"></div>
103 | </div>
104 | ` : ''}
105 | <div class="tool-inputs">
106 | ${!onlyUriTools.includes(tool.name) ? `
107 | <input type="number" id="line-${tool.name}" placeholder="Line number" style="width: 100px">
108 | <input type="number" id="char-${tool.name}" placeholder="Character" style="width: 100px">
109 | ` : ''}
110 | ${tool.name === 'get_completions' ? `
111 | <input type="text" id="trigger-${tool.name}" placeholder="Trigger character" style="width: 50px" maxlength="1">
112 | ` : ''}
113 | ${tool.name === 'get_rename_locations' || tool.name === 'rename' ? `
114 | <input type="text" id="newname-${tool.name}" placeholder="New name" style="width: 150px">
115 | ` : ''}
116 | ${tool.name === 'get_workspace_symbols' ? `
117 | <input type="text" id="query-${tool.name}" placeholder="Search symbols..." style="width: 200px">
118 | ` : ''}
119 | </div>
120 | <button onclick="executeTool('${tool.name}')">Execute</button>
121 | <pre id="result-${tool.name}">Results will appear here...</pre>
122 | </div>
123 | `).join('')}
124 | <script>
125 | const vscode = acquireVsCodeApi();
126 | let workspaceFiles = [];
127 |
128 | // File autocomplete functionality
129 | function setupFileAutocomplete(toolName) {
130 | if (${JSON.stringify(noUriTools)}.includes(toolName)) {
131 | return;
132 | }
133 | const input = document.getElementById('uri-' + toolName);
134 | const autocompleteList = document.getElementById('autocomplete-' + toolName);
135 | let selectedIndex = -1;
136 |
137 | input.addEventListener('input', () => {
138 | const value = input.value.toLowerCase();
139 | const matches = workspaceFiles.filter(file =>
140 | file.toLowerCase().includes(value)
141 | ).slice(0, 10);
142 |
143 | if (matches.length && value) {
144 | autocompleteList.innerHTML = matches
145 | .map((file, index) => \`
146 | <div class="autocomplete-item" data-index="\${index}">
147 | \${file}
148 | </div>
149 | \`).join('');
150 | autocompleteList.style.display = 'block';
151 | } else {
152 | autocompleteList.style.display = 'none';
153 | }
154 | selectedIndex = -1;
155 | });
156 |
157 | input.addEventListener('keydown', (e) => {
158 | const items = autocompleteList.getElementsByClassName('autocomplete-item');
159 |
160 | if (e.key === 'ArrowDown') {
161 | e.preventDefault();
162 | selectedIndex = Math.min(selectedIndex + 1, items.length - 1);
163 | updateSelection();
164 | } else if (e.key === 'ArrowUp') {
165 | e.preventDefault();
166 | selectedIndex = Math.max(selectedIndex - 1, -1);
167 | updateSelection();
168 | } else if (e.key === 'Enter' && selectedIndex >= 0) {
169 | e.preventDefault();
170 | if (items[selectedIndex]) {
171 | input.value = items[selectedIndex].textContent.trim();
172 | autocompleteList.style.display = 'none';
173 | }
174 | } else if (e.key === 'Escape') {
175 | autocompleteList.style.display = 'none';
176 | selectedIndex = -1;
177 | }
178 | });
179 |
180 | function updateSelection() {
181 | const items = autocompleteList.getElementsByClassName('autocomplete-item');
182 | for (let i = 0; i < items.length; i++) {
183 | items[i].classList.toggle('selected', i === selectedIndex);
184 | }
185 | if (selectedIndex >= 0 && items[selectedIndex]) {
186 | items[selectedIndex].scrollIntoView({ block: 'nearest' });
187 | }
188 | }
189 |
190 | autocompleteList.addEventListener('click', (e) => {
191 | const item = e.target.closest('.autocomplete-item');
192 | if (item) {
193 | input.value = item.textContent.trim();
194 | autocompleteList.style.display = 'none';
195 | }
196 | });
197 |
198 | document.addEventListener('click', (e) => {
199 | if (!e.target.closest('.autocomplete-container')) {
200 | autocompleteList.style.display = 'none';
201 | }
202 | });
203 | }
204 |
205 | tools = ${JSON.stringify(toolsDescriptions)};
206 | tools.forEach(tool => {
207 | setupFileAutocomplete(tool.name);
208 | });
209 |
210 | function useCurrentFile(toolName) {
211 | if (${JSON.stringify(noUriTools)}.includes(toolName)) {
212 | return;
213 | }
214 | vscode.postMessage({
215 | command: 'getCurrentFile',
216 | tool: toolName
217 | });
218 | }
219 |
220 | function executeTool(toolName) {
221 | const params = {};
222 |
223 | if (!${JSON.stringify(noUriTools)}.includes(toolName)) {
224 | const uri = document.getElementById('uri-' + toolName).value;
225 | params.textDocument = { uri };
226 | }
227 |
228 | if (!${JSON.stringify(onlyUriTools)}.includes(toolName)) {
229 | const line = document.getElementById('line-' + toolName)?.value;
230 | const char = document.getElementById('char-' + toolName)?.value;
231 | params.position = {
232 | line: parseInt(line),
233 | character: parseInt(char)
234 | };
235 | }
236 |
237 | if (toolName === 'get_completions') {
238 | const trigger = document.getElementById('trigger-' + toolName)?.value;
239 | if (trigger) {
240 | params.triggerCharacter = trigger;
241 | }
242 | }
243 |
244 | if (toolName === 'get_rename_locations' || toolName === 'rename') {
245 | const newName = document.getElementById('newname-' + toolName)?.value;
246 | if (newName) {
247 | params.newName = newName;
248 | }
249 | }
250 |
251 | if (toolName === 'get_workspace_symbols') {
252 | const query = document.getElementById('query-' + toolName)?.value;
253 | params.query = query || '';
254 | }
255 |
256 | vscode.postMessage({
257 | command: 'execute',
258 | tool: toolName,
259 | params
260 | });
261 | }
262 |
263 | window.addEventListener('message', event => {
264 | const message = event.data;
265 | if (message.type === 'files') {
266 | workspaceFiles = message.files;
267 | } else if (message.type === 'currentFile') {
268 | const input = document.getElementById('uri-' + message.tool);
269 | const resultElement = document.getElementById('result-' + message.tool);
270 | if (message.error) {
271 | resultElement.textContent = message.error + '. Please open a file in the editor first.';
272 | resultElement.className = 'error-message';
273 | input.value = '';
274 | } else if (message.uri) {
275 | input.value = message.uri;
276 | resultElement.textContent = 'Current file selected: ' + message.uri;
277 | resultElement.className = 'success-message';
278 | }
279 | } else if (message.type === 'result') {
280 | const resultElement = document.getElementById('result-' + message.tool);
281 | try {
282 | let resultText = '';
283 | if (message.result?.content?.[0]?.type === 'text') {
284 | const innerContent = message.result.content[0].text;
285 | try {
286 | const parsedJson = JSON.parse(innerContent);
287 | resultText = JSON.stringify(parsedJson, null, 2);
288 | } catch {
289 | resultText = JSON.stringify(message.result, null, 2);
290 | }
291 | } else {
292 | resultText = JSON.stringify(message.result, null, 2);
293 | }
294 | resultElement.textContent = resultText;
295 | resultElement.className = ''; // Remove any special styling for results
296 | } catch (error) {
297 | resultElement.textContent = JSON.stringify(message.result, null, 2);
298 | resultElement.className = ''; // Remove any special styling for results
299 | }
300 | }
301 | });
302 | </script>
303 | </body>
304 | </html>
305 | `;
```
--------------------------------------------------------------------------------
/src/toolRunner.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 | import { createVscodePosition, getPreview, convertSymbol, asyncMap, convertSemanticTokens, getSymbolKindString, transformLocations, transformSingleLocation } from './helpers';
3 | import { ReferencesAndPreview, RenameEdit } from './rosyln';
4 | import { mcpTools } from './tools';
5 |
6 | const toolNames = mcpTools.map((tool) => tool.name);
7 |
8 | export const runTool = async (name: string, args: any) => {
9 | let result: any;
10 | if (!toolNames.includes(name)) {
11 | throw new Error(`Unknown tool: ${name}`);
12 | }
13 | // Verify file exists before proceeding
14 | const uri = vscode.Uri.parse(args?.textDocument?.uri ?? '');
15 | try {
16 | await vscode.workspace.fs.stat(uri);
17 | } catch (error) {
18 | return {
19 | content: [{
20 | type: "text",
21 | text: `Error: File not found - ${uri.fsPath}`
22 | }],
23 | isError: true
24 | };
25 | }
26 |
27 | const position = args?.position ? createVscodePosition(
28 | args.position.line,
29 | args.position.character
30 | ) : undefined;
31 |
32 | let command: string;
33 | let commandResult: any;
34 |
35 | switch (name) {
36 | case "find_usages":
37 | command = 'vscode.executeReferenceProvider';
38 | const locations = await vscode.commands.executeCommand<vscode.Location[]>(
39 | command,
40 | uri,
41 | position
42 | );
43 |
44 | if (!locations) {
45 | result = [];
46 | break;
47 | }
48 | const references: ReferencesAndPreview[] = await asyncMap(
49 | locations,
50 | transformSingleLocation
51 | );
52 | result = references;
53 | break;
54 |
55 | case "go_to_definition":
56 | command = 'vscode.executeDefinitionProvider';
57 | commandResult = await vscode.commands.executeCommand(command, uri, position);
58 | result = await transformLocations(commandResult);
59 | break;
60 |
61 | case "find_implementations":
62 | command = 'vscode.executeImplementationProvider';
63 | commandResult = await vscode.commands.executeCommand(command, uri, position);
64 | result = await transformLocations(commandResult);
65 | break;
66 |
67 | case "get_hover_info":
68 | command = 'vscode.executeHoverProvider';
69 | commandResult = await vscode.commands.executeCommand(command, uri, position);
70 | result = await asyncMap(commandResult, async (hover: vscode.Hover) => ({
71 | contents: hover.contents.map(content =>
72 | typeof content === 'string' ? content : content.value
73 | ),
74 | range: hover.range ? {
75 | start: {
76 | line: hover.range.start.line,
77 | character: hover.range.start.character
78 | },
79 | end: {
80 | line: hover.range.end.line,
81 | character: hover.range.end.character
82 | }
83 | } : undefined,
84 | preview: await getPreview(uri, hover.range?.start.line)
85 | }));
86 | break;
87 |
88 | case "get_document_symbols":
89 | command = 'vscode.executeDocumentSymbolProvider';
90 | commandResult = await vscode.commands.executeCommand(command, uri);
91 | result = commandResult?.map(convertSymbol);
92 | break;
93 |
94 | case "get_completions":
95 | const completions = await vscode.commands.executeCommand<vscode.CompletionList>(
96 | 'vscode.executeCompletionItemProvider',
97 | uri,
98 | position,
99 | args?.triggerCharacter
100 | );
101 | result = completions?.items.map(item => ({
102 | label: item.label,
103 | kind: item.kind,
104 | detail: item.detail,
105 | documentation: item.documentation,
106 | sortText: item.sortText,
107 | filterText: item.filterText,
108 | insertText: item.insertText,
109 | range: item.range && ('start' in item.range) ? {
110 | start: {
111 | line: item.range.start.line,
112 | character: item.range.start.character
113 | },
114 | end: {
115 | line: item.range.end.line,
116 | character: item.range.end.character
117 | }
118 | } : undefined
119 | }));
120 | break;
121 |
122 | case "get_signature_help":
123 | const signatureHelp = await vscode.commands.executeCommand<vscode.SignatureHelp>(
124 | 'vscode.executeSignatureHelpProvider',
125 | uri,
126 | position
127 | );
128 | result = signatureHelp?.signatures.map(sig => ({
129 | label: sig.label,
130 | documentation: sig.documentation,
131 | parameters: sig.parameters?.map(param => ({
132 | label: param.label,
133 | documentation: param.documentation
134 | })),
135 | activeParameter: signatureHelp.activeParameter,
136 | activeSignature: signatureHelp.activeSignature
137 | }));
138 | break;
139 |
140 | case "get_rename_locations": {
141 | const newName = args?.newName || "newName";
142 | const renameEdits = await vscode.commands.executeCommand<vscode.WorkspaceEdit>(
143 | 'vscode.executeDocumentRenameProvider',
144 | uri,
145 | position,
146 | newName
147 | );
148 | if (renameEdits) {
149 | const entries: RenameEdit[] = [];
150 | for (const [editUri, edits] of renameEdits.entries()) {
151 | entries.push({
152 | uri: editUri.toString(),
153 | edits: edits.map(edit => ({
154 | range: {
155 | start: {
156 | line: edit.range.start.line,
157 | character: edit.range.start.character
158 | },
159 | end: {
160 | line: edit.range.end.line,
161 | character: edit.range.end.character
162 | }
163 | },
164 | newText: edit.newText
165 | }))
166 | });
167 | }
168 | result = entries;
169 | } else {
170 | result = [];
171 | }
172 | break;
173 | }
174 |
175 | case "rename": {
176 | const newName = args?.newName || "newName";
177 | const renameEdits = await vscode.commands.executeCommand<vscode.WorkspaceEdit>(
178 | 'vscode.executeDocumentRenameProvider',
179 | uri,
180 | position,
181 | newName
182 | );
183 | if (renameEdits) {
184 | const success = await vscode.workspace.applyEdit(renameEdits);
185 | return {
186 | content: [{
187 | type: "text",
188 | text: success ? "Symbol renamed successfully" : "Symbol renaming failed"
189 | }],
190 | isError: false
191 | };
192 | } else {
193 | return {
194 | content: [{
195 | type: "text",
196 | text: "Symbol to rename not found"
197 | }],
198 | isError: false
199 | };
200 | }
201 | break;
202 | }
203 |
204 | case "get_code_actions":
205 | const codeActions = await vscode.commands.executeCommand<vscode.CodeAction[]>(
206 | 'vscode.executeCodeActionProvider',
207 | uri,
208 | position ? new vscode.Range(position, position) : undefined
209 | );
210 | result = codeActions?.map(action => ({
211 | title: action.title,
212 | kind: action.kind?.value,
213 | isPreferred: action.isPreferred,
214 | diagnostics: action.diagnostics?.map(diag => ({
215 | message: diag.message,
216 | severity: diag.severity,
217 | range: {
218 | start: {
219 | line: diag.range.start.line,
220 | character: diag.range.start.character
221 | },
222 | end: {
223 | line: diag.range.end.line,
224 | character: diag.range.end.character
225 | }
226 | }
227 | }))
228 | }));
229 | break;
230 |
231 | case "get_code_lens":
232 | const codeLensUri = vscode.Uri.parse((args as any).textDocument?.uri);
233 | try {
234 | const codeLensResult = await vscode.commands.executeCommand<vscode.CodeLens[]>(
235 | 'vscode.executeCodeLensProvider',
236 | codeLensUri
237 | );
238 |
239 | if (!codeLensResult || codeLensResult.length === 0) {
240 | return {
241 | content: [{
242 | type: "text",
243 | text: "No CodeLens items found in document"
244 | }],
245 | isError: false
246 | };
247 | }
248 |
249 | result = codeLensResult.map(lens => ({
250 | range: {
251 | start: {
252 | line: lens.range.start.line,
253 | character: lens.range.start.character
254 | },
255 | end: {
256 | line: lens.range.end.line,
257 | character: lens.range.end.character
258 | }
259 | },
260 | command: lens.command ? {
261 | title: lens.command.title,
262 | command: lens.command.command,
263 | arguments: lens.command.arguments
264 | } : undefined
265 | }));
266 | } catch (error) {
267 | return {
268 | content: [{
269 | type: "text",
270 | text: `Error executing CodeLens provider: ${error}`
271 | }],
272 | isError: true
273 | };
274 | }
275 | break;
276 |
277 | case "get_selection_range":
278 | const selectionRanges = await vscode.commands.executeCommand<vscode.SelectionRange[]>(
279 | 'vscode.executeSelectionRangeProvider',
280 | uri,
281 | [position]
282 | );
283 | result = selectionRanges?.map(range => ({
284 | range: {
285 | start: {
286 | line: range.range.start.line,
287 | character: range.range.start.character
288 | },
289 | end: {
290 | line: range.range.end.line,
291 | character: range.range.end.character
292 | }
293 | },
294 | parent: range.parent ? {
295 | range: {
296 | start: {
297 | line: range.parent.range.start.line,
298 | character: range.parent.range.start.character
299 | },
300 | end: {
301 | line: range.parent.range.end.line,
302 | character: range.parent.range.end.character
303 | }
304 | }
305 | } : undefined
306 | }));
307 | break;
308 |
309 | case "get_type_definition":
310 | command = 'vscode.executeTypeDefinitionProvider';
311 | commandResult = await vscode.commands.executeCommand(command, uri, position);
312 | result = await transformLocations(commandResult);
313 | break;
314 |
315 | case "get_declaration":
316 | command = 'vscode.executeDeclarationProvider';
317 | commandResult = await vscode.commands.executeCommand(command, uri, position);
318 | result = await transformLocations(commandResult);
319 | break;
320 |
321 | case "get_document_highlights":
322 | const highlights = await vscode.commands.executeCommand<vscode.DocumentHighlight[]>(
323 | 'vscode.executeDocumentHighlights',
324 | uri,
325 | position
326 | );
327 | result = highlights?.map(highlight => ({
328 | range: {
329 | start: {
330 | line: highlight.range.start.line,
331 | character: highlight.range.start.character
332 | },
333 | end: {
334 | line: highlight.range.end.line,
335 | character: highlight.range.end.character
336 | }
337 | },
338 | kind: highlight.kind
339 | }));
340 | break;
341 |
342 | case "get_workspace_symbols":
343 | const query = args.query || '';
344 | const symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
345 | 'vscode.executeWorkspaceSymbolProvider',
346 | query
347 | );
348 | result = symbols?.map(symbol => ({
349 | name: symbol.name,
350 | kind: symbol.kind,
351 | location: {
352 | uri: symbol.location.uri.toString(),
353 | range: {
354 | start: {
355 | line: symbol.location.range.start.line,
356 | character: symbol.location.range.start.character
357 | },
358 | end: {
359 | line: symbol.location.range.end.line,
360 | character: symbol.location.range.end.character
361 | }
362 | }
363 | },
364 | containerName: symbol.containerName
365 | }));
366 | break;
367 |
368 | case "get_semantic_tokens":
369 | const semanticTokensUri = vscode.Uri.parse((args as any).textDocument?.uri);
370 |
371 | // Check if semantic tokens provider is available
372 | const providers = await vscode.languages.getLanguages();
373 | const document = await vscode.workspace.openTextDocument(semanticTokensUri);
374 | const hasSemanticTokens = providers.includes(document.languageId);
375 |
376 | if (!hasSemanticTokens) {
377 | return {
378 | content: [{
379 | type: "text",
380 | text: `Semantic tokens not supported for language: ${document.languageId}`
381 | }],
382 | isError: true
383 | };
384 | }
385 |
386 | try {
387 | const semanticTokens = await vscode.commands.executeCommand<vscode.SemanticTokens>(
388 | 'vscode.provideDocumentSemanticTokens',
389 | semanticTokensUri
390 | );
391 |
392 | if (!semanticTokens) {
393 | return {
394 | content: [{
395 | type: "text",
396 | text: "No semantic tokens found in document"
397 | }],
398 | isError: false
399 | };
400 | }
401 |
402 | // Convert to human-readable format
403 | const readableTokens = convertSemanticTokens(semanticTokens, document);
404 |
405 | result = {
406 | resultId: semanticTokens.resultId,
407 | tokens: readableTokens
408 | };
409 | } catch (error) {
410 | // If the command is not found, try alternative approach
411 | const tokenTypes = [
412 | 'namespace', 'class', 'enum', 'interface',
413 | 'struct', 'typeParameter', 'type', 'parameter',
414 | 'variable', 'property', 'enumMember', 'decorator',
415 | 'event', 'function', 'method', 'macro', 'keyword',
416 | 'modifier', 'comment', 'string', 'number', 'regexp',
417 | 'operator'
418 | ];
419 |
420 | // Use document symbols as fallback
421 | const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
422 | 'vscode.executeDocumentSymbolProvider',
423 | semanticTokensUri
424 | );
425 |
426 | if (symbols) {
427 | result = {
428 | fallback: "Using document symbols as fallback",
429 | symbols: symbols.map(symbol => ({
430 | name: symbol.name,
431 | kind: symbol.kind,
432 | range: {
433 | start: {
434 | line: symbol.range.start.line,
435 | character: symbol.range.start.character
436 | },
437 | end: {
438 | line: symbol.range.end.line,
439 | character: symbol.range.end.character
440 | }
441 | },
442 | tokenType: tokenTypes[symbol.kind] || 'unknown'
443 | }))
444 | };
445 | } else {
446 | return {
447 | content: [{
448 | type: "text",
449 | text: "Semantic tokens provider not available and fallback failed"
450 | }],
451 | isError: true
452 | };
453 | }
454 | }
455 | break;
456 |
457 | case "get_call_hierarchy":
458 | const callHierarchyItems = await vscode.commands.executeCommand<vscode.CallHierarchyItem[]>(
459 | 'vscode.prepareCallHierarchy',
460 | uri,
461 | position
462 | );
463 |
464 | if (callHierarchyItems?.[0]) {
465 | const [incomingCalls, outgoingCalls] = await Promise.all([
466 | vscode.commands.executeCommand<vscode.CallHierarchyIncomingCall[]>(
467 | 'vscode.executeCallHierarchyIncomingCalls',
468 | callHierarchyItems[0]
469 | ),
470 | vscode.commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>(
471 | 'vscode.executeCallHierarchyOutgoingCalls',
472 | callHierarchyItems[0]
473 | )
474 | ]);
475 |
476 | result = {
477 | item: {
478 | name: callHierarchyItems[0].name,
479 | kind: getSymbolKindString(callHierarchyItems[0].kind),
480 | detail: callHierarchyItems[0].detail,
481 | uri: callHierarchyItems[0].uri.toString(),
482 | range: {
483 | start: {
484 | line: callHierarchyItems[0].range.start.line,
485 | character: callHierarchyItems[0].range.start.character
486 | },
487 | end: {
488 | line: callHierarchyItems[0].range.end.line,
489 | character: callHierarchyItems[0].range.end.character
490 | }
491 | }
492 | },
493 | incomingCalls: incomingCalls?.map(call => ({
494 | from: {
495 | name: call.from.name,
496 | kind: getSymbolKindString(call.from.kind),
497 | uri: call.from.uri.toString(),
498 | range: {
499 | start: {
500 | line: call.from.range.start.line,
501 | character: call.from.range.start.character
502 | },
503 | end: {
504 | line: call.from.range.end.line,
505 | character: call.from.range.end.character
506 | }
507 | }
508 | },
509 | fromRanges: call.fromRanges.map(range => ({
510 | start: {
511 | line: range.start.line,
512 | character: range.start.character
513 | },
514 | end: {
515 | line: range.end.line,
516 | character: range.end.character
517 | }
518 | }))
519 | })),
520 | outgoingCalls: outgoingCalls?.map(call => ({
521 | to: {
522 | name: call.to.name,
523 | kind: getSymbolKindString(call.to.kind),
524 | uri: call.to.uri.toString(),
525 | range: {
526 | start: {
527 | line: call.to.range.start.line,
528 | character: call.to.range.start.character
529 | },
530 | end: {
531 | line: call.to.range.end.line,
532 | character: call.to.range.end.character
533 | }
534 | }
535 | },
536 | fromRanges: call.fromRanges.map(range => ({
537 | start: {
538 | line: range.start.line,
539 | character: range.start.character
540 | },
541 | end: {
542 | line: range.end.line,
543 | character: range.end.character
544 | }
545 | }))
546 | }))
547 | };
548 | }
549 | break;
550 |
551 | case "get_type_hierarchy":
552 | const typeHierarchyItems = await vscode.commands.executeCommand<vscode.TypeHierarchyItem[]>(
553 | 'vscode.prepareTypeHierarchy',
554 | uri,
555 | position
556 | );
557 |
558 | if (typeHierarchyItems?.[0]) {
559 | const [supertypes, subtypes] = await Promise.all([
560 | vscode.commands.executeCommand<vscode.TypeHierarchyItem[]>(
561 | 'vscode.executeTypeHierarchySupertypeCommand',
562 | typeHierarchyItems[0]
563 | ),
564 | vscode.commands.executeCommand<vscode.TypeHierarchyItem[]>(
565 | 'vscode.executeTypeHierarchySubtypeCommand',
566 | typeHierarchyItems[0]
567 | )
568 | ]);
569 |
570 | result = {
571 | item: {
572 | name: typeHierarchyItems[0].name,
573 | kind: getSymbolKindString(typeHierarchyItems[0].kind),
574 | detail: typeHierarchyItems[0].detail,
575 | uri: typeHierarchyItems[0].uri.toString(),
576 | range: {
577 | start: {
578 | line: typeHierarchyItems[0].range.start.line,
579 | character: typeHierarchyItems[0].range.start.character
580 | },
581 | end: {
582 | line: typeHierarchyItems[0].range.end.line,
583 | character: typeHierarchyItems[0].range.end.character
584 | }
585 | }
586 | },
587 | supertypes: supertypes?.map(type => ({
588 | name: type.name,
589 | kind: getSymbolKindString(type.kind),
590 | detail: type.detail,
591 | uri: type.uri.toString(),
592 | range: {
593 | start: {
594 | line: type.range.start.line,
595 | character: type.range.start.character
596 | },
597 | end: {
598 | line: type.range.end.line,
599 | character: type.range.end.character
600 | }
601 | }
602 | })),
603 | subtypes: subtypes?.map(type => ({
604 | name: type.name,
605 | kind: getSymbolKindString(type.kind),
606 | detail: type.detail,
607 | uri: type.uri.toString(),
608 | range: {
609 | start: {
610 | line: type.range.start.line,
611 | character: type.range.start.character
612 | },
613 | end: {
614 | line: type.range.end.line,
615 | character: type.range.end.character
616 | }
617 | }
618 | }))
619 | };
620 | }
621 | break;
622 |
623 | default:
624 | throw new Error(`Unknown tool: ${name}`);
625 | }
626 | return result;
627 | }
628 |
```
--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const mcpTools = [
2 | {
3 | name: "find_usages",
4 | description:
5 | "Finds all references to a symbol at a specified location in code. This tool helps you identify where functions, variables, types, or other symbols are used throughout the codebase. " +
6 | "It performs a deep semantic analysis to find true references, not just text matches. " +
7 | "The results include:\n" +
8 | "- Complete file path for each reference\n" +
9 | "- Precise location (line and character position)\n" +
10 | "- Context preview showing how the symbol is used\n" +
11 | "- Optional inclusion of the symbol's declaration\n\n" +
12 | "This is particularly useful for:\n" +
13 | "- Understanding dependencies between different parts of the code\n" +
14 | "- Safely planning refactoring operations\n" +
15 | "- Analyzing the impact of potential changes\n" +
16 | "- Tracing data flow through the application\n\n" +
17 | "Note: Line numbers are 0-based (first line is 0), while character positions are 0-based (first character is 0).",
18 | inputSchema: {
19 | type: "object",
20 | properties: {
21 | textDocument: {
22 | type: "object",
23 | description: "The document containing the symbol",
24 | properties: {
25 | uri: {
26 | type: "string",
27 | description: "URI of the document (file:///path/to/file format)"
28 | }
29 | },
30 | required: ["uri"]
31 | },
32 | position: {
33 | type: "object",
34 | description: "The position of the symbol",
35 | properties: {
36 | line: {
37 | type: "number",
38 | description: "One-based line number"
39 | },
40 | character: {
41 | type: "number",
42 | description: "Zero-based character position"
43 | }
44 | },
45 | required: ["line", "character"]
46 | },
47 | context: {
48 | type: "object",
49 | description: "Additional context for the request",
50 | properties: {
51 | includeDeclaration: {
52 | type: "boolean",
53 | description: "Whether to include the declaration of the symbol in the results",
54 | default: true
55 | }
56 | }
57 | }
58 | },
59 | required: ["textDocument", "position"]
60 | }
61 | },
62 | {
63 | name: "go_to_definition",
64 | description: "Navigates to the original definition of a symbol at a specified location in code. " +
65 | "This tool performs semantic analysis to find the true source definition, not just matching text. It can locate:\n" +
66 | "- Function/method declarations\n" +
67 | "- Class/interface definitions\n" +
68 | "- Variable declarations\n" +
69 | "- Type definitions\n" +
70 | "- Import/module declarations\n\n" +
71 | "The tool is essential for:\n" +
72 | "- Understanding where code elements are defined\n" +
73 | "- Navigating complex codebases\n" +
74 | "- Verifying the actual implementation of interfaces/abstractions\n\n" +
75 | "Note: Line numbers are 0-based (first line is 0), while character positions are 0-based (first character is 0).",
76 | inputSchema: {
77 | type: "object",
78 | properties: {
79 | textDocument: {
80 | type: "object",
81 | description: "The document containing the symbol",
82 | properties: {
83 | uri: {
84 | type: "string",
85 | description: "URI of the document"
86 | }
87 | },
88 | required: ["uri"]
89 | },
90 | position: {
91 | type: "object",
92 | description: "The position of the symbol",
93 | properties: {
94 | line: {
95 | type: "number",
96 | description: "One-based line number"
97 | },
98 | character: {
99 | type: "number",
100 | description: "Zero-based character position"
101 | }
102 | },
103 | required: ["line", "character"]
104 | }
105 | },
106 | required: ["textDocument", "position"]
107 | }
108 | },
109 | {
110 | name: "find_implementations",
111 | description: "Discovers all concrete implementations of an interface, abstract class, or abstract method in the codebase. " +
112 | "This tool performs deep semantic analysis to find all places where:\n" +
113 | "- Interfaces are implemented by classes\n" +
114 | "- Abstract classes are extended\n" +
115 | "- Abstract methods are overridden\n" +
116 | "- Virtual methods are overridden\n\n" +
117 | "This is particularly valuable for:\n" +
118 | "- Understanding polymorphic behavior in the codebase\n" +
119 | "- Finding all concrete implementations of an interface\n" +
120 | "- Analyzing inheritance hierarchies\n" +
121 | "- Verifying contract implementations\n\n" +
122 | "Note: Line numbers are 0-based (first line is 0), while character positions are 0-based (first character is 0).",
123 | inputSchema: {
124 | type: "object",
125 | properties: {
126 | textDocument: {
127 | type: "object",
128 | description: "The document containing the symbol",
129 | properties: {
130 | uri: {
131 | type: "string",
132 | description: "URI of the document"
133 | }
134 | },
135 | required: ["uri"]
136 | },
137 | position: {
138 | type: "object",
139 | description: "The position of the symbol",
140 | properties: {
141 | line: {
142 | type: "number",
143 | description: "One-based line number"
144 | },
145 | character: {
146 | type: "number",
147 | description: "Zero-based character position"
148 | }
149 | },
150 | required: ["line", "character"]
151 | }
152 | },
153 | required: ["textDocument", "position"]
154 | }
155 | },
156 | {
157 | name: "get_hover_info",
158 | description: "Retrieves comprehensive information about a symbol when hovering over it in code. " +
159 | "This tool provides rich contextual details including:\n" +
160 | "- Full type information and signatures\n" +
161 | "- Documentation comments and summaries\n" +
162 | "- Return types and parameter descriptions\n" +
163 | "- Type constraints and generic parameters\n" +
164 | "- Deprecation notices and version information\n\n" +
165 | "This is especially useful for:\n" +
166 | "- Understanding API usage and requirements\n" +
167 | "- Viewing documentation without leaving the context\n" +
168 | "- Verifying type information during development\n" +
169 | "- Quick access to symbol metadata\n\n" +
170 | "Note: Line numbers are 0-based (first line is 0), while character positions are 0-based (first character is 0).",
171 | inputSchema: {
172 | type: "object",
173 | properties: {
174 | textDocument: {
175 | type: "object",
176 | description: "The document containing the symbol",
177 | properties: {
178 | uri: {
179 | type: "string",
180 | description: "URI of the document"
181 | }
182 | },
183 | required: ["uri"]
184 | },
185 | position: {
186 | type: "object",
187 | description: "The position of the symbol",
188 | properties: {
189 | line: {
190 | type: "number",
191 | description: "One-based line number"
192 | },
193 | character: {
194 | type: "number",
195 | description: "Zero-based character position"
196 | }
197 | },
198 | required: ["line", "character"]
199 | }
200 | },
201 | required: ["textDocument", "position"]
202 | }
203 | },
204 | {
205 | name: "get_document_symbols",
206 | description: "Analyzes and returns a hierarchical list of all symbols defined within a document. " +
207 | "This tool provides a comprehensive overview of the code structure by identifying:\n" +
208 | "- Classes and interfaces\n" +
209 | "- Methods and functions\n" +
210 | "- Properties and fields\n" +
211 | "- Namespaces and modules\n" +
212 | "- Constants and enumerations\n\n" +
213 | "The symbols are returned in a structured format that preserves their relationships and scope. " +
214 | "This is particularly useful for:\n" +
215 | "- Understanding the overall structure of a file\n" +
216 | "- Creating code outlines and documentation\n" +
217 | "- Navigating large files efficiently\n" +
218 | "- Analyzing code organization and architecture",
219 | inputSchema: {
220 | type: "object",
221 | properties: {
222 | textDocument: {
223 | type: "object",
224 | description: "The document to analyze",
225 | properties: {
226 | uri: {
227 | type: "string",
228 | description: "URI of the document"
229 | }
230 | },
231 | required: ["uri"]
232 | }
233 | },
234 | required: ["textDocument"]
235 | }
236 | },
237 | {
238 | name: "get_completions",
239 | description: "Provides intelligent code completion suggestions based on the current context and cursor position. " +
240 | "This tool analyzes the code to offer relevant suggestions including:\n" +
241 | "- Variable and function names\n" +
242 | "- Class and type names\n" +
243 | "- Property and method completions\n" +
244 | "- Import statements\n" +
245 | "- Snippets and common patterns\n\n" +
246 | "The suggestions are context-aware and can be triggered by:\n" +
247 | "- Typing part of a symbol name\n" +
248 | "- Accessing object properties (.)\n" +
249 | "- Opening brackets or parentheses\n" +
250 | "- Language-specific triggers\n\n" +
251 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
252 | inputSchema: {
253 | type: "object",
254 | properties: {
255 | textDocument: {
256 | type: "object",
257 | description: "The document to get completions for",
258 | properties: {
259 | uri: {
260 | type: "string",
261 | description: "URI of the document"
262 | }
263 | },
264 | required: ["uri"]
265 | },
266 | position: {
267 | type: "object",
268 | description: "The position to get completions at",
269 | properties: {
270 | line: {
271 | type: "number",
272 | description: "Zero-based line number"
273 | },
274 | character: {
275 | type: "number",
276 | description: "Zero-based character position"
277 | }
278 | },
279 | required: ["line", "character"]
280 | },
281 | triggerCharacter: {
282 | type: "string",
283 | description: "Optional trigger character that caused completion"
284 | }
285 | },
286 | required: ["textDocument", "position"]
287 | }
288 | },
289 | {
290 | name: "get_signature_help",
291 | description: "Provides detailed information about function signatures as you type function calls. " +
292 | "This tool offers real-time assistance with:\n" +
293 | "- Parameter names and types\n" +
294 | "- Parameter documentation\n" +
295 | "- Overload information\n" +
296 | "- Return type details\n" +
297 | "- Generic type constraints\n\n" +
298 | "The signature help is context-sensitive and updates as you type, showing:\n" +
299 | "- Currently active parameter\n" +
300 | "- Available overloads\n" +
301 | "- Type compatibility information\n" +
302 | "- Optional and default values\n\n" +
303 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
304 | inputSchema: {
305 | type: "object",
306 | properties: {
307 | textDocument: {
308 | type: "object",
309 | description: "The document to get signature help for",
310 | properties: {
311 | uri: {
312 | type: "string",
313 | description: "URI of the document"
314 | }
315 | },
316 | required: ["uri"]
317 | },
318 | position: {
319 | type: "object",
320 | description: "The position to get signature help at",
321 | properties: {
322 | line: {
323 | type: "number",
324 | description: "Zero-based line number"
325 | },
326 | character: {
327 | type: "number",
328 | description: "Zero-based character position"
329 | }
330 | },
331 | required: ["line", "character"]
332 | }
333 | },
334 | required: ["textDocument", "position"]
335 | }
336 | },
337 | {
338 | name: "get_rename_locations",
339 | description: "Identifies all locations that need to be updated when renaming a symbol. " +
340 | "This tool performs a comprehensive analysis to ensure safe and accurate renaming by:\n" +
341 | "- Finding all references to the symbol\n" +
342 | "- Checking for naming conflicts\n" +
343 | "- Analyzing scope boundaries\n" +
344 | "- Identifying related declarations\n\n" +
345 | "The tool is particularly valuable for:\n" +
346 | "- Safe refactoring operations\n" +
347 | "- Cross-file symbol renaming\n" +
348 | "- Impact analysis before renaming\n" +
349 | "- Maintaining code consistency\n\n" +
350 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
351 | inputSchema: {
352 | type: "object",
353 | properties: {
354 | textDocument: {
355 | type: "object",
356 | description: "The document containing the symbol to rename",
357 | properties: {
358 | uri: {
359 | type: "string",
360 | description: "URI of the document"
361 | }
362 | },
363 | required: ["uri"]
364 | },
365 | position: {
366 | type: "object",
367 | description: "The position of the symbol to rename",
368 | properties: {
369 | line: {
370 | type: "number",
371 | description: "Zero-based line number"
372 | },
373 | character: {
374 | type: "number",
375 | description: "Zero-based character position"
376 | }
377 | },
378 | required: ["line", "character"]
379 | },
380 | newName: {
381 | type: "string",
382 | description: "The new name for the symbol"
383 | }
384 | },
385 | required: ["textDocument", "position"]
386 | }
387 | },
388 | {
389 | name: "rename",
390 | description: "Identifies all locations that need to be updated when renaming a symbol, and performs the renaming. " +
391 | "This tool performs a comprehensive analysis to ensure safe and accurate renaming by:\n" +
392 | "- Finding all references to the symbol\n" +
393 | "- Checking for naming conflicts\n" +
394 | "- Analyzing scope boundaries\n" +
395 | "- Identifying related declarations\n\n" +
396 | "The tool is particularly valuable for:\n" +
397 | "- Safe refactoring operations\n" +
398 | "- Cross-file symbol renaming\n" +
399 | "- Maintaining code consistency\n\n" +
400 | "- Renaming without the need to generate code\n\n" +
401 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
402 | inputSchema: {
403 | type: "object",
404 | properties: {
405 | textDocument: {
406 | type: "object",
407 | description: "The document containing the symbol to rename",
408 | properties: {
409 | uri: {
410 | type: "string",
411 | description: "URI of the document"
412 | }
413 | },
414 | required: ["uri"]
415 | },
416 | position: {
417 | type: "object",
418 | description: "The position of the symbol to rename",
419 | properties: {
420 | line: {
421 | type: "number",
422 | description: "Zero-based line number"
423 | },
424 | character: {
425 | type: "number",
426 | description: "Zero-based character position"
427 | }
428 | },
429 | required: ["line", "character"]
430 | },
431 | newName: {
432 | type: "string",
433 | description: "The new name for the symbol"
434 | }
435 | },
436 | required: ["textDocument", "position", "newName"]
437 | }
438 | },
439 | {
440 | name: "get_code_actions",
441 | description: "Provides context-aware code actions and refactoring suggestions at a specified location. " +
442 | "This tool analyzes the code to offer intelligent improvements such as:\n" +
443 | "- Quick fixes for errors and warnings\n" +
444 | "- Code refactoring options\n" +
445 | "- Import management suggestions\n" +
446 | "- Code style improvements\n" +
447 | "- Performance optimizations\n\n" +
448 | "Available actions may include:\n" +
449 | "- Extract method/variable/constant\n" +
450 | "- Implement interface members\n" +
451 | "- Add missing imports\n" +
452 | "- Convert code constructs\n" +
453 | "- Fix code style issues\n\n" +
454 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
455 | inputSchema: {
456 | type: "object",
457 | properties: {
458 | textDocument: {
459 | type: "object",
460 | description: "The document to get code actions for",
461 | properties: {
462 | uri: {
463 | type: "string",
464 | description: "URI of the document"
465 | }
466 | },
467 | required: ["uri"]
468 | },
469 | position: {
470 | type: "object",
471 | description: "The position to get code actions at",
472 | properties: {
473 | line: {
474 | type: "number",
475 | description: "Zero-based line number"
476 | },
477 | character: {
478 | type: "number",
479 | description: "Zero-based character position"
480 | }
481 | },
482 | required: ["line", "character"]
483 | }
484 | },
485 | required: ["textDocument"]
486 | }
487 | },
488 | {
489 | name: "get_semantic_tokens",
490 | description: "Provides detailed semantic token information for enhanced code understanding and highlighting. " +
491 | "This tool performs deep analysis to identify and classify code elements:\n" +
492 | "- Variables and their scopes\n" +
493 | "- Function and method names\n" +
494 | "- Type names and annotations\n" +
495 | "- Keywords and operators\n" +
496 | "- Comments and documentation\n\n" +
497 | "The semantic information enables:\n" +
498 | "- Precise syntax highlighting\n" +
499 | "- Code navigation improvements\n" +
500 | "- Better code understanding\n" +
501 | "- Accurate symbol classification\n" +
502 | "- Enhanced code analysis",
503 | inputSchema: {
504 | type: "object",
505 | properties: {
506 | textDocument: {
507 | type: "object",
508 | description: "The document to get semantic tokens for",
509 | properties: {
510 | uri: {
511 | type: "string",
512 | description: "URI of the document"
513 | }
514 | },
515 | required: ["uri"]
516 | }
517 | },
518 | required: ["textDocument"]
519 | }
520 | },
521 | {
522 | name: "get_call_hierarchy",
523 | description: "Analyzes and visualizes the call relationships between functions and methods in the codebase. " +
524 | "This tool builds a comprehensive call graph showing:\n" +
525 | "- Incoming calls (who calls this function)\n" +
526 | "- Outgoing calls (what this function calls)\n" +
527 | "- Call chains and dependencies\n" +
528 | "- Recursive call patterns\n\n" +
529 | "This information is invaluable for:\n" +
530 | "- Understanding code flow and dependencies\n" +
531 | "- Analyzing impact of changes\n" +
532 | "- Debugging complex call chains\n" +
533 | "- Optimizing function relationships\n" +
534 | "- Identifying potential refactoring targets\n\n" +
535 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
536 | inputSchema: {
537 | type: "object",
538 | properties: {
539 | textDocument: {
540 | type: "object",
541 | description: "The document containing the function",
542 | properties: {
543 | uri: {
544 | type: "string",
545 | description: "URI of the document"
546 | }
547 | },
548 | required: ["uri"]
549 | },
550 | position: {
551 | type: "object",
552 | description: "The position of the function",
553 | properties: {
554 | line: {
555 | type: "number",
556 | description: "Zero-based line number"
557 | },
558 | character: {
559 | type: "number",
560 | description: "Zero-based character position"
561 | }
562 | },
563 | required: ["line", "character"]
564 | }
565 | },
566 | required: ["textDocument", "position"]
567 | }
568 | },
569 | {
570 | name: "get_type_hierarchy",
571 | description: "Analyzes and visualizes the inheritance and implementation relationships between types. " +
572 | "This tool creates a comprehensive type hierarchy showing:\n" +
573 | "- Parent classes and interfaces\n" +
574 | "- Child classes and implementations\n" +
575 | "- Interface inheritance chains\n" +
576 | "- Mixin and trait relationships\n\n" +
577 | "The hierarchy information is crucial for:\n" +
578 | "- Understanding class relationships\n" +
579 | "- Analyzing inheritance patterns\n" +
580 | "- Planning class structure changes\n" +
581 | "- Identifying potential abstraction opportunities\n" +
582 | "- Verifying type system design\n\n" +
583 | "Note: Line numbers are 0-based (first line is 0), and character positions are 0-based (first character is 0).",
584 | inputSchema: {
585 | type: "object",
586 | properties: {
587 | textDocument: {
588 | type: "object",
589 | description: "The document containing the type",
590 | properties: {
591 | uri: {
592 | type: "string",
593 | description: "URI of the document"
594 | }
595 | },
596 | required: ["uri"]
597 | },
598 | position: {
599 | type: "object",
600 | description: "The position of the type",
601 | properties: {
602 | line: {
603 | type: "number",
604 | description: "Zero-based line number"
605 | },
606 | character: {
607 | type: "number",
608 | description: "Zero-based character position"
609 | }
610 | },
611 | required: ["line", "character"]
612 | }
613 | },
614 | required: ["textDocument", "position"]
615 | }
616 | },
617 | {
618 | name: "get_code_lens",
619 | description: "Gets CodeLens information for a document, showing actionable contextual information inline with code",
620 | inputSchema: {
621 | type: "object",
622 | properties: {
623 | textDocument: {
624 | type: "object",
625 | description: "The document to get CodeLens for",
626 | properties: {
627 | uri: {
628 | type: "string",
629 | description: "URI of the document"
630 | }
631 | },
632 | required: ["uri"]
633 | }
634 | },
635 | required: ["textDocument"]
636 | }
637 | },
638 | {
639 | name: "get_selection_range",
640 | description: "Gets selection ranges for a position in a document. This helps in smart selection expansion based on semantic document structure.",
641 | inputSchema: {
642 | type: "object",
643 | properties: {
644 | textDocument: {
645 | type: "object",
646 | description: "The document to analyze",
647 | properties: {
648 | uri: {
649 | type: "string",
650 | description: "URI of the document"
651 | }
652 | },
653 | required: ["uri"]
654 | },
655 | position: {
656 | type: "object",
657 | description: "The position to get selection ranges for",
658 | properties: {
659 | line: {
660 | type: "number",
661 | description: "Zero-based line number"
662 | },
663 | character: {
664 | type: "number",
665 | description: "Zero-based character position"
666 | }
667 | },
668 | required: ["line", "character"]
669 | }
670 | },
671 | required: ["textDocument", "position"]
672 | }
673 | },
674 | {
675 | name: "get_type_definition",
676 | description: "Finds type definitions of a symbol at a specified location. This is particularly useful for finding the underlying type definitions of variables, interfaces, and classes.",
677 | inputSchema: {
678 | type: "object",
679 | properties: {
680 | textDocument: {
681 | type: "object",
682 | description: "The document containing the symbol",
683 | properties: {
684 | uri: {
685 | type: "string",
686 | description: "URI of the document"
687 | }
688 | },
689 | required: ["uri"]
690 | },
691 | position: {
692 | type: "object",
693 | description: "The position of the symbol",
694 | properties: {
695 | line: {
696 | type: "number",
697 | description: "Zero-based line number"
698 | },
699 | character: {
700 | type: "number",
701 | description: "Zero-based character position"
702 | }
703 | },
704 | required: ["line", "character"]
705 | }
706 | },
707 | required: ["textDocument", "position"]
708 | }
709 | },
710 | {
711 | name: "get_declaration",
712 | description: "Finds declarations of a symbol at a specified location. This helps in navigating to where symbols are declared, particularly useful for imported symbols.",
713 | inputSchema: {
714 | type: "object",
715 | properties: {
716 | textDocument: {
717 | type: "object",
718 | description: "The document containing the symbol",
719 | properties: {
720 | uri: {
721 | type: "string",
722 | description: "URI of the document"
723 | }
724 | },
725 | required: ["uri"]
726 | },
727 | position: {
728 | type: "object",
729 | description: "The position of the symbol",
730 | properties: {
731 | line: {
732 | type: "number",
733 | description: "Zero-based line number"
734 | },
735 | character: {
736 | type: "number",
737 | description: "Zero-based character position"
738 | }
739 | },
740 | required: ["line", "character"]
741 | }
742 | },
743 | required: ["textDocument", "position"]
744 | }
745 | },
746 | {
747 | name: "get_document_highlights",
748 | description: "Finds all highlights of a symbol in a document. This is useful for highlighting all occurrences of a symbol within the current document.",
749 | inputSchema: {
750 | type: "object",
751 | properties: {
752 | textDocument: {
753 | type: "object",
754 | description: "The document to analyze",
755 | properties: {
756 | uri: {
757 | type: "string",
758 | description: "URI of the document"
759 | }
760 | },
761 | required: ["uri"]
762 | },
763 | position: {
764 | type: "object",
765 | description: "The position of the symbol",
766 | properties: {
767 | line: {
768 | type: "number",
769 | description: "Zero-based line number"
770 | },
771 | character: {
772 | type: "number",
773 | description: "Zero-based character position"
774 | }
775 | },
776 | required: ["line", "character"]
777 | }
778 | },
779 | required: ["textDocument", "position"]
780 | }
781 | },
782 | {
783 | name: "get_workspace_symbols",
784 | description: "Searches for symbols across the entire workspace. This is useful for finding symbols by name across all files. Especially useful for finding the file and positions of a symbol to use in other tools.",
785 | inputSchema: {
786 | type: "object",
787 | properties: {
788 | query: {
789 | type: "string",
790 | description: "The search query for finding symbols"
791 | }
792 | },
793 | required: ["query"]
794 | }
795 | }
796 | ];
797 |
798 | export const toolsDescriptions = [
799 | {
800 | name: "find_usages",
801 | description: "Find all references to a symbol"
802 | },
803 | {
804 | name: "go_to_definition",
805 | description: "Find definition of a symbol"
806 | },
807 | {
808 | name: "find_implementations",
809 | description: "Find implementations of interface/abstract method"
810 | },
811 | {
812 | name: "get_hover_info",
813 | description: "Get hover information for a symbol"
814 | },
815 | {
816 | name: "get_document_symbols",
817 | description: "Get all symbols in document"
818 | },
819 | {
820 | name: "get_completions",
821 | description: "Get code completion suggestions at a position"
822 | },
823 | {
824 | name: "get_signature_help",
825 | description: "Get function signature information"
826 | },
827 | {
828 | name: "get_rename_locations",
829 | description: "Get all locations that would be affected by renaming a symbol"
830 | },
831 | {
832 | name: "rename",
833 | description: "Rename a symbol"
834 | },
835 | {
836 | name: "get_code_actions",
837 | description: "Get available code actions and refactorings"
838 | },
839 | {
840 | name: "get_semantic_tokens",
841 | description: "Get semantic token information for code understanding"
842 | },
843 | {
844 | name: "get_call_hierarchy",
845 | description: "Get incoming and outgoing call hierarchy"
846 | },
847 | {
848 | name: "get_type_hierarchy",
849 | description: "Get type hierarchy information"
850 | },
851 | {
852 | name: "get_code_lens",
853 | description: "Gets CodeLens information for a document, showing actionable contextual information inline with code"
854 | },
855 | {
856 | name: "get_selection_range",
857 | description: "Gets selection ranges for smart selection expansion"
858 | },
859 | {
860 | name: "get_type_definition",
861 | description: "Find type definitions of symbols"
862 | },
863 | {
864 | name: "get_declaration",
865 | description: "Find declarations of symbols"
866 | },
867 | {
868 | name: "get_document_highlights",
869 | description: "Find all highlights of a symbol in document"
870 | },
871 | {
872 | name: "get_workspace_symbols",
873 | description: "Search for symbols across the workspace"
874 | }
875 | ];
```