From 34481e70997b4dcee38109f76e232e3d9d8ac871 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 14:15:33 +0200 Subject: [PATCH 1/6] Dedicated llms-install.md (for easier setup with Cline and similar) --- llms-install.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 llms-install.md diff --git a/llms-install.md b/llms-install.md new file mode 100644 index 00000000..b02711c7 --- /dev/null +++ b/llms-install.md @@ -0,0 +1,29 @@ +# MCP Installation instructions + +This document is mainly used as instructions for AI-assistants like Cline and others that +try to do an automatic install based on freeform instructions. + +0. Make sure `uv` is installed. If not, install it using either `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS, Linux) or + `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` (Windows). Find the path to the `uv` executable, + you'll need it later. +1. Clone the repo with `git clone git@github.com:oraios/serena.git` and change into its dir (e.g., `cd serena`) +2. Check if `serena_config.yml` exists. If not, create it with `cp serena_config.template.yml serena_config.yml`. Read the instructions in the config. +3. In the config, check if the path to your project was added. If not, add it to the `projects` section +4. In your project, create a `.serena` if needed and check whether `project.yml` exists there. +5. If no `project.yml` was found, create it using `cp /path/to/serena/myproject.template.yml /path/to/your/project/.serena/project.yml` +6. Read the instructions in `project.yml`. Make sure the `project.yml` has the correct project language configured. + Remove the project_root entry there. +7. Finally, add the Serena MCP server config like this: + +```json + { + "mcpServers": { + ... + "serena": { + "command": "/abs/path/to/uv", + "args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server", "/path/to/your/project/.serena/project.yml"] + } + } + } + +``` From 787d1584609b26e07a6f504a8f3d5a4e17d48124 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 14:19:50 +0200 Subject: [PATCH 2/6] Disable GUI by default (again) See https://github.com/oraios/serena/issues/39 --- serena_config.template.yml | 2 +- src/serena/agent.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/serena_config.template.yml b/serena_config.template.yml index 5aa220b9..90853f01 100644 --- a/serena_config.template.yml +++ b/serena_config.template.yml @@ -31,7 +31,7 @@ projects: # # Limitations: doesn't seem to work with the community version of Claude Desktop for Linux # Might also cause problems with some MCP clients - if you have any issues, try disabling this -gui_log_window: True +gui_log_window: False # minimum log level for the GUI log window (10 = debug, 20 = info, 30 = warning, 40 = error) gui_log_level: 20 diff --git a/src/serena/agent.py b/src/serena/agent.py index 769429f5..ddb882b5 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -125,7 +125,7 @@ class SerenaConfig: self.projects[project_config.project_name] = project_config self.project_names = list(self.projects.keys()) - self.gui_log_window_enabled = config_yaml.get("gui_log_window", True) + self.gui_log_window_enabled = config_yaml.get("gui_log_window", False) self.gui_log_window_level = config_yaml.get("gui_log_level", logging.INFO) self.enable_project_activation = config_yaml.get("enable_project_activation", True) From a6fd83f82974994db2fd1647f9c1bc22798edd86 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 15:10:52 +0200 Subject: [PATCH 3/6] LS: fix isfile check (was on relative path) --- src/multilspy/language_server.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index f3874469..6668628f 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -866,14 +866,19 @@ class LanguageServer: :return: A list of root symbols representing the top-level packages/modules in the project. """ - if within_relative_path is not None and os.path.isfile(within_relative_path): - if self.should_ignore_path(within_relative_path): - self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) - return [] - - _, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body) - return root_nodes + if within_relative_path is not None: + within_abs_path = os.path.join(self.repository_root_path, within_relative_path) + if not os.path.exists(within_abs_path): + raise FileNotFoundError(f"File or directory not found: {within_abs_path}") + if os.path.isfile(within_abs_path): + if self.should_ignore_path(within_relative_path): + self.logger.log(f"You passed a file explicitly, but it is ignored. This is probably an error. File: {within_relative_path}", logging.ERROR) + return [] + else: + _, root_nodes = await self.request_document_symbols(within_relative_path, include_body=include_body) + return root_nodes + # Helper function to recursively process directories async def process_directory(dir_path: str) -> List[multilspy_types.UnifiedSymbolInformation]: abs_dir_path = self.repository_root_path if dir_path == "." else os.path.join(self.repository_root_path, dir_path) From 664d959eb8548aebae73208338ef2d95e74a1ece Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 15:38:46 +0200 Subject: [PATCH 4/6] LS: small utility method request_overview --- src/multilspy/language_server.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/multilspy/language_server.py b/src/multilspy/language_server.py index 6668628f..b298a461 100644 --- a/src/multilspy/language_server.py +++ b/src/multilspy/language_server.py @@ -1025,6 +1025,23 @@ class LanguageServer: for root in document_roots ] + async def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + """ + An overview of all symbols in the given file or directory. + + :param within_relative_path: the relative path to the file or directory to get the overview of. + :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. + """ + abs_path = (Path(self.repository_root_path) / within_relative_path).resolve() + if not abs_path.exists(): + raise FileNotFoundError(f"File or directory not found: {abs_path}") + + if abs_path.is_file(): + symbols_overview = await self.request_document_overview(within_relative_path) + return {within_relative_path: symbols_overview} + else: + return await self.request_dir_overview(within_relative_path) + async def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server @@ -1743,7 +1760,20 @@ class SyncLanguageServer: self.language_server.request_document_overview(relative_file_path), self.loop ).result() return result - + + def request_overview(self, within_relative_path: str) -> dict[str, list[tuple[str, multilspy_types.SymbolKind, int, int]]]: + """ + An overview of all symbols in the given file or directory. + + :param within_relative_path: the relative path to the file or directory to get the overview of. + :return: A mapping of all relative paths analyzed to lists of tuples (name, kind, line, column) of all top-level symbols in the corresponding file. + """ + assert self.loop + result = asyncio.run_coroutine_threadsafe( + self.language_server.request_overview(within_relative_path), self.loop + ).result() + return result + def request_hover(self, relative_file_path: str, line: int, column: int) -> Union[multilspy_types.Hover, None]: """ Raise a [textDocument/hover](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) request to the Language Server From 5d3e973ea8323f072017c853ddc2c724128cc4c1 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 15:39:19 +0200 Subject: [PATCH 5/6] Merged the overview tools into a single tool --- README.md | 3 +-- src/serena/agent.py | 37 ++++++++----------------------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 01724720..6056b90a 100644 --- a/README.md +++ b/README.md @@ -584,8 +584,7 @@ Here is the full list of Serena's tools with a short description (output of `uv * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). * `get_active_project`: Gets the name of the currently active project (if any) and lists existing projects -* `get_dir_overview`: Gets an overview of the top-level symbols defined in all files within a given directory. -* `get_document_overview`: Gets an overview of the top-level symbols defined in a given file. +* `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory. * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. * `insert_at_line`: Inserts content at a given line in a file. * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. diff --git a/src/serena/agent.py b/src/serena/agent.py index ddb882b5..e13b1afb 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -590,25 +590,26 @@ class ListDirTool(Tool): return self._limit_length(result, max_answer_chars) -class GetDirOverviewTool(Tool): +class GetSymbolsOverviewTool(Tool): """ - Gets an overview of the top-level symbols defined in all files within a given directory. + Gets an overview of the top-level symbols defined in a given file or directory. """ def apply(self, relative_path: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: """ - Gets an overview of the given directory. - For each file in the directory, we list the top-level symbols in the file (name, kind, line). - Use this tool to get a high-level understanding of the code symbols inside a directory. + Gets an overview of the given file or directory. + For each analyzed file, we list the top-level symbols in the file (name, kind, line). + Use this tool to get a high-level understanding of the code symbols. + Calling this is often a good idea before more targeted reading, searching or editing operations on the code symbols. - :param relative_path: the relative path to the directory to get the overview of + :param relative_path: the relative path to the file or directory to get the overview of :param max_answer_chars: if the overview is longer than this number of characters, no content will be returned. Don't adjust unless there is really no other way to get the content required for the task. If the overview is too long, you should use a smaller directory instead, (e.g. a subdirectory). :return: a JSON object mapping relative paths of all contained files to info about top-level symbols in the file (name, kind, line, column). """ - path_to_symbol_infos = self.language_server.request_dir_overview(relative_path) + path_to_symbol_infos = self.language_server.request_overview(relative_path) result = {} for file_path, symbols in path_to_symbol_infos.items(): result[file_path] = [_tuple_to_info(*symbol_info) for symbol_info in symbols] @@ -617,28 +618,6 @@ class GetDirOverviewTool(Tool): return self._limit_length(result_json_str, max_answer_chars) -class GetDocumentOverviewTool(Tool): - """ - Gets an overview of the top-level symbols defined in a given file. - """ - - def apply(self, relative_path: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: - """ - Use this tool to get a high-level understanding of the code symbols in a file. It often makes sense - to call this before targeted reading, searching or editing operations on the code symbols in the file, - as the output will contain a lot of information about names and lines. - - :param relative_path: the relative path to the file to get the overview of - :param max_answer_chars: if the overview is longer than this number of characters, - no content will be returned. Don't adjust unless there is really no other way to get the content - required for the task. - :return: a JSON object with the info (name, kind, line, column) of all top-level symbols in the file. - """ - result = self.language_server.request_document_overview(relative_path) - result_json_str = json.dumps([_tuple_to_info(*symbol_info) for symbol_info in result]) - return self._limit_length(result_json_str, max_answer_chars) - - class FindSymbolTool(Tool): """ Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). From 784e85781b033e6c797eefb848267dbc74d239ea Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Tue, 8 Apr 2025 15:55:39 +0200 Subject: [PATCH 6/6] Changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d348cf67..19605d73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,14 @@ Changes prior to the next official version change will appear here. +* Serena core: + * bugfix in find_symbol tool (a bug fixed in LS) + * merged the two overview tools (for dir and file) int a single one + * one-click setup for Cline enabled + * Language Servers: - * Add further file extensions considered by the language servers for Python (.pyi), JavaScript (.jsx) and TypeScript (.tsx, .jsx) + * Add further file extensions considered by the language servers for Python (.pyi), JavaScript (.jsx) and TypeScript (.tsx, .jsx) + # 2025-04-07