#
tokens: 48839/50000 69/119 files (page 1/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 5. Use http://codebase.md/wrale/mcp-server-tree-sitter?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .codestateignore
├── .github
│   └── workflows
│       ├── ci.yml
│       └── release.yml
├── .gitignore
├── .python-version
├── CONTRIBUTING.md
├── docs
│   ├── architecture.md
│   ├── cli.md
│   ├── config.md
│   ├── diagnostics.md
│   ├── logging.md
│   ├── requirements
│   │   └── logging.md
│   └── tree-sitter-type-safety.md
├── FEATURES.md
├── LICENSE
├── Makefile
├── NOTICE
├── pyproject.toml
├── README.md
├── ROADMAP.md
├── scripts
│   └── implementation-search.sh
├── src
│   └── mcp_server_tree_sitter
│       ├── __init__.py
│       ├── __main__.py
│       ├── api.py
│       ├── bootstrap
│       │   ├── __init__.py
│       │   └── logging_bootstrap.py
│       ├── cache
│       │   ├── __init__.py
│       │   └── parser_cache.py
│       ├── capabilities
│       │   ├── __init__.py
│       │   └── server_capabilities.py
│       ├── config.py
│       ├── context.py
│       ├── di.py
│       ├── exceptions.py
│       ├── language
│       │   ├── __init__.py
│       │   ├── query_templates.py
│       │   ├── registry.py
│       │   └── templates
│       │       ├── __init__.py
│       │       ├── apl.py
│       │       ├── c.py
│       │       ├── cpp.py
│       │       ├── go.py
│       │       ├── java.py
│       │       ├── javascript.py
│       │       ├── julia.py
│       │       ├── kotlin.py
│       │       ├── python.py
│       │       ├── rust.py
│       │       ├── swift.py
│       │       └── typescript.py
│       ├── logging_config.py
│       ├── models
│       │   ├── __init__.py
│       │   ├── ast_cursor.py
│       │   ├── ast.py
│       │   └── project.py
│       ├── prompts
│       │   ├── __init__.py
│       │   └── code_patterns.py
│       ├── server.py
│       ├── testing
│       │   ├── __init__.py
│       │   └── pytest_diagnostic.py
│       ├── tools
│       │   ├── __init__.py
│       │   ├── analysis.py
│       │   ├── ast_operations.py
│       │   ├── debug.py
│       │   ├── file_operations.py
│       │   ├── project.py
│       │   ├── query_builder.py
│       │   ├── registration.py
│       │   └── search.py
│       └── utils
│           ├── __init__.py
│           ├── context
│           │   ├── __init__.py
│           │   └── mcp_context.py
│           ├── file_io.py
│           ├── path.py
│           ├── security.py
│           ├── tree_sitter_helpers.py
│           └── tree_sitter_types.py
├── tests
│   ├── __init__.py
│   ├── .gitignore
│   ├── conftest.py
│   ├── test_ast_cursor.py
│   ├── test_basic.py
│   ├── test_cache_config.py
│   ├── test_cli_arguments.py
│   ├── test_config_behavior.py
│   ├── test_config_manager.py
│   ├── test_context.py
│   ├── test_debug_flag.py
│   ├── test_di.py
│   ├── test_diagnostics
│   │   ├── __init__.py
│   │   ├── test_ast_parsing.py
│   │   ├── test_ast.py
│   │   ├── test_cursor_ast.py
│   │   ├── test_language_pack.py
│   │   ├── test_language_registry.py
│   │   └── test_unpacking_errors.py
│   ├── test_env_config.py
│   ├── test_failure_modes.py
│   ├── test_file_operations.py
│   ├── test_helpers.py
│   ├── test_language_listing.py
│   ├── test_logging_bootstrap.py
│   ├── test_logging_config_di.py
│   ├── test_logging_config.py
│   ├── test_logging_early_init.py
│   ├── test_logging_env_vars.py
│   ├── test_logging_handlers.py
│   ├── test_makefile_targets.py
│   ├── test_mcp_context.py
│   ├── test_models_ast.py
│   ├── test_persistent_server.py
│   ├── test_project_persistence.py
│   ├── test_query_result_handling.py
│   ├── test_registration.py
│   ├── test_rust_compatibility.py
│   ├── test_server_capabilities.py
│   ├── test_server.py
│   ├── test_symbol_extraction.py
│   ├── test_tree_sitter_helpers.py
│   ├── test_yaml_config_di.py
│   └── test_yaml_config.py
├── TODO.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.12
2 | 
```

--------------------------------------------------------------------------------
/.codestateignore:
--------------------------------------------------------------------------------

```
1 | uv.lock
2 | 
```

--------------------------------------------------------------------------------
/tests/.gitignore:
--------------------------------------------------------------------------------

```
1 | # Reports
2 | *.json
3 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Byte-compiled / optimized / DLL files
  2 | __pycache__/
  3 | *.py[cod]
  4 | *$py.class
  5 | 
  6 | # C extensions
  7 | *.so
  8 | 
  9 | # Distribution / packaging
 10 | .Python
 11 | build/
 12 | develop-eggs/
 13 | dist/
 14 | downloads/
 15 | eggs/
 16 | .eggs/
 17 | lib/
 18 | lib64/
 19 | parts/
 20 | sdist/
 21 | var/
 22 | wheels/
 23 | share/python-wheels/
 24 | *.egg-info/
 25 | .installed.cfg
 26 | *.egg
 27 | MANIFEST
 28 | 
 29 | # PyInstaller
 30 | #  Usually these files are written by a python script from a template
 31 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 32 | *.manifest
 33 | *.spec
 34 | 
 35 | # Installer logs
 36 | pip-log.txt
 37 | pip-delete-this-directory.txt
 38 | 
 39 | # Unit test / coverage reports
 40 | htmlcov/
 41 | .tox/
 42 | .nox/
 43 | .coverage
 44 | .coverage.*
 45 | .cache
 46 | nosetests.xml
 47 | coverage.xml
 48 | *.cover
 49 | *.py,cover
 50 | .hypothesis/
 51 | .pytest_cache/
 52 | cover/
 53 | 
 54 | # Translations
 55 | *.mo
 56 | *.pot
 57 | 
 58 | # Django stuff:
 59 | *.log
 60 | local_settings.py
 61 | db.sqlite3
 62 | db.sqlite3-journal
 63 | 
 64 | # Flask stuff:
 65 | instance/
 66 | .webassets-cache
 67 | 
 68 | # Scrapy stuff:
 69 | .scrapy
 70 | 
 71 | # Sphinx documentation
 72 | docs/_build/
 73 | 
 74 | # PyBuilder
 75 | .pybuilder/
 76 | target/
 77 | 
 78 | # Jupyter Notebook
 79 | .ipynb_checkpoints
 80 | 
 81 | # IPython
 82 | profile_default/
 83 | ipython_config.py
 84 | 
 85 | # pyenv
 86 | #   For a library or package, you might want to ignore these files since the code is
 87 | #   intended to run in multiple environments; otherwise, check them in:
 88 | # .python-version
 89 | 
 90 | # pipenv
 91 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 92 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
 93 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
 94 | #   install all needed dependencies.
 95 | #Pipfile.lock
 96 | 
 97 | # UV
 98 | #   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
 99 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
100 | #   commonly ignored for libraries.
101 | #uv.lock
102 | 
103 | # poetry
104 | #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
106 | #   commonly ignored for libraries.
107 | #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 | 
110 | # pdm
111 | #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | #   in version control.
115 | #   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 | 
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 | 
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 | 
127 | # SageMath parsed files
128 | *.sage.py
129 | 
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 | 
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 | 
143 | # Rope project settings
144 | .ropeproject
145 | 
146 | # mkdocs documentation
147 | /site
148 | 
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 | 
154 | # Pyre type checker
155 | .pyre/
156 | 
157 | # pytype static type analyzer
158 | .pytype/
159 | 
160 | # Cython debug symbols
161 | cython_debug/
162 | 
163 | # PyCharm
164 | #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | #  and can be added to the global gitignore or merged into this file.  For a more nuclear
167 | #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 | 
170 | # Ruff stuff:
171 | .ruff_cache/
172 | 
173 | # PyPI configuration file
174 | .pypirc
175 | 
176 | # etc.
177 | results/
178 | diagnostic_results/
179 | *.json
180 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/wrale-mcp-server-tree-sitter-badge.png)](https://mseep.ai/app/wrale-mcp-server-tree-sitter)
  2 | 
  3 | # MCP Tree-sitter Server
  4 | 
  5 | A Model Context Protocol (MCP) server that provides code analysis capabilities using tree-sitter, designed to give AI assistants intelligent access to codebases with appropriate context management. Claude Desktop is the reference implementation target.
  6 | 
  7 | <a href="https://glama.ai/mcp/servers/@wrale/mcp-server-tree-sitter">
  8 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/@wrale/mcp-server-tree-sitter/badge" alt="mcp-server-tree-sitter MCP server" />
  9 | </a>
 10 | 
 11 | ## Features
 12 | 
 13 | - 🔍 **Flexible Exploration**: Examine code at multiple levels of granularity
 14 | - 🧠 **Context Management**: Provides just enough information without overwhelming the context window
 15 | - 🌐 **Language Agnostic**: Supports many programming languages including Python, JavaScript, TypeScript, Go, Rust, C, C++, Swift, Java, Kotlin, Julia, and APL via tree-sitter-language-pack
 16 | - 🌳 **Structure-Aware**: Uses AST-based understanding with efficient cursor-based traversal
 17 | - 🔎 **Searchable**: Find specific patterns using text search and tree-sitter queries
 18 | - 🔄 **Caching**: Optimized performance through parse tree caching
 19 | - 🔑 **Symbol Extraction**: Extract and analyze functions, classes, and other code symbols
 20 | - 📊 **Dependency Analysis**: Identify and analyze code dependencies and relationships
 21 | - 🧩 **State Persistence**: Maintains project registrations and cached data between invocations
 22 | - 🔒 **Secure**: Built-in security boundaries and input validation
 23 | 
 24 | For a comprehensive list of all available commands, their current implementation status, and detailed feature matrix, please refer to the [FEATURES.md](FEATURES.md) document.
 25 | 
 26 | ## Installation
 27 | 
 28 | ### Prerequisites
 29 | 
 30 | - Python 3.10+
 31 | - Tree-sitter language parsers for your preferred languages
 32 | 
 33 | ### Basic Installation
 34 | 
 35 | ```bash
 36 | pip install mcp-server-tree-sitter
 37 | ```
 38 | 
 39 | ### Development Installation
 40 | 
 41 | ```bash
 42 | git clone https://github.com/wrale/mcp-server-tree-sitter.git
 43 | cd mcp-server-tree-sitter
 44 | pip install -e ".[dev,languages]"
 45 | ```
 46 | 
 47 | ## Quick Start
 48 | 
 49 | ### Running with Claude Desktop
 50 | 
 51 | You can make the server available in Claude Desktop either through the MCP CLI or by manually configuring Claude Desktop.
 52 | 
 53 | #### Using MCP CLI
 54 | 
 55 | Register the server with Claude Desktop:
 56 | 
 57 | ```bash
 58 | mcp install mcp_server_tree_sitter.server:mcp --name "tree_sitter"
 59 | ```
 60 | 
 61 | #### Manual Configuration
 62 | 
 63 | Alternatively, you can manually configure Claude Desktop:
 64 | 
 65 | 1. Open your Claude Desktop configuration file:
 66 |    - macOS/Linux: `~/Library/Application Support/Claude/claude_desktop_config.json`
 67 |    - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
 68 |    
 69 |    Create the file if it doesn't exist.
 70 | 
 71 | 2. Add the server to the `mcpServers` section:
 72 | 
 73 |    ```json
 74 |    {
 75 |        "mcpServers": {
 76 |            "tree_sitter": {
 77 |                "command": "python",
 78 |                "args": [
 79 |                    "-m",
 80 |                    "mcp_server_tree_sitter.server"
 81 |                ]
 82 |            }
 83 |        }
 84 |    }
 85 |    ```
 86 | 
 87 |    Alternatively, if using uv or another package manager:
 88 | 
 89 |    ```json
 90 |    {
 91 |        "mcpServers": {
 92 |            "tree_sitter": {
 93 |                "command": "uv",
 94 |                "args": [
 95 |                    "--directory",
 96 |                    "/ABSOLUTE/PATH/TO/YOUR/PROJECT",
 97 |                    "run",
 98 |                    "-m",
 99 |                    "mcp_server_tree_sitter.server"
100 |                ]
101 |            }
102 |        }
103 |    }
104 |    ```
105 | 
106 |    Note: Make sure to replace `/ABSOLUTE/PATH/TO/YOUR/PROJECT` with the actual absolute path to your project directory.
107 | 
108 | 3. Save the file and restart Claude Desktop.
109 | 
110 | The MCP tools icon (hammer) will appear in Claude Desktop's interface once you have properly configured at least one MCP server. You can then access the `tree_sitter` server's functionality by clicking on this icon.
111 | 
112 | ### Configuring with Released Version
113 | 
114 | If you prefer not to manually install the package from PyPI (released version) or clone the repository, simply use the following configuration for Claude Desktop:
115 | 
116 | 1. Open your Claude Desktop configuration file (same location as above).
117 | 
118 | 2. Add the tree-sitter server to the `mcpServers` section:
119 | 
120 |    ```json
121 |    {
122 |        "mcpServers": {
123 |            "tree_sitter": {
124 |                "command": "uvx",
125 |                "args": [
126 |                    "--directory", "/ABSOLUTE/PATH/TO/YOUR/PROJECT",
127 |                    "mcp-server-tree-sitter"
128 |                ]
129 |            }
130 |        }
131 |    }
132 |    ```
133 | 
134 | 3. Save the file and restart Claude Desktop.
135 | 
136 | This method uses `uvx` to run the installed PyPI package directly, which is the recommended approach for the released version. The server doesn't require any additional parameters to run in its basic configuration.
137 | 
138 | ## State Persistence
139 | 
140 | The MCP Tree-sitter Server maintains state between invocations. This means:
141 | - Projects stay registered until explicitly removed or the server is restarted
142 | - Parse trees are cached according to configuration settings
143 | - Language information is retained throughout the server's lifetime
144 | 
145 | This persistence is maintained in-memory during the server's lifetime using singleton patterns for key components.
146 | 
147 | ### Running as a standalone server
148 | 
149 | There are several ways to run the server:
150 | 
151 | #### Using the MCP CLI directly:
152 | 
153 | ```bash
154 | python -m mcp run mcp_server_tree_sitter.server
155 | ```
156 | 
157 | #### Using Makefile targets:
158 | 
159 | ```bash
160 | # Show available targets
161 | make
162 | 
163 | # Run the server with default settings
164 | make mcp-run
165 | 
166 | # Show help information
167 | make mcp-run ARGS="--help"
168 | 
169 | # Show version information
170 | make mcp-run ARGS="--version"
171 | 
172 | # Run with custom configuration file
173 | make mcp-run ARGS="--config /path/to/config.yaml"
174 | 
175 | # Enable debug logging
176 | make mcp-run ARGS="--debug"
177 | 
178 | # Disable parse tree caching
179 | make mcp-run ARGS="--disable-cache"
180 | ```
181 | 
182 | #### Using the installed script:
183 | 
184 | ```bash
185 | # Run the server with default settings
186 | mcp-server-tree-sitter
187 | 
188 | # Show help information
189 | mcp-server-tree-sitter --help
190 | 
191 | # Show version information
192 | mcp-server-tree-sitter --version
193 | 
194 | # Run with custom configuration file
195 | mcp-server-tree-sitter --config /path/to/config.yaml
196 | 
197 | # Enable debug logging
198 | mcp-server-tree-sitter --debug
199 | 
200 | # Disable parse tree caching
201 | mcp-server-tree-sitter --disable-cache
202 | ```
203 | 
204 | ### Using with the MCP Inspector
205 | 
206 | Using the MCP CLI directly:
207 | 
208 | ```bash
209 | python -m mcp dev mcp_server_tree_sitter.server
210 | ```
211 | 
212 | Or using the Makefile target:
213 | 
214 | ```bash
215 | make mcp-dev
216 | ```
217 | 
218 | You can also pass arguments:
219 | 
220 | ```bash
221 | make mcp-dev ARGS="--debug"
222 | ```
223 | 
224 | ## Usage
225 | 
226 | ### Register a Project
227 | 
228 | First, register a project to analyze:
229 | 
230 | ```
231 | register_project_tool(path="/path/to/your/project", name="my-project")
232 | ```
233 | 
234 | ### Explore Files
235 | 
236 | List files in the project:
237 | 
238 | ```
239 | list_files(project="my-project", pattern="**/*.py")
240 | ```
241 | 
242 | View file content:
243 | 
244 | ```
245 | get_file(project="my-project", path="src/main.py")
246 | ```
247 | 
248 | ### Analyze Code Structure
249 | 
250 | Get the syntax tree:
251 | 
252 | ```
253 | get_ast(project="my-project", path="src/main.py", max_depth=3)
254 | ```
255 | 
256 | Extract symbols:
257 | 
258 | ```
259 | get_symbols(project="my-project", path="src/main.py")
260 | ```
261 | 
262 | ### Search Code
263 | 
264 | Search for text:
265 | 
266 | ```
267 | find_text(project="my-project", pattern="function", file_pattern="**/*.py")
268 | ```
269 | 
270 | Run tree-sitter queries:
271 | 
272 | ```
273 | run_query(
274 |     project="my-project",
275 |     query='(function_definition name: (identifier) @function.name)',
276 |     language="python"
277 | )
278 | ```
279 | 
280 | ### Analyze Complexity
281 | 
282 | ```
283 | analyze_complexity(project="my-project", path="src/main.py")
284 | ```
285 | 
286 | ## Direct Python Usage
287 | 
288 | While the primary intended use is through the MCP server, you can also use the library directly in Python code:
289 | 
290 | ```python
291 | # Import from the API module
292 | from mcp_server_tree_sitter.api import (
293 |     register_project, list_projects, get_config, get_language_registry
294 | )
295 | 
296 | # Register a project
297 | project_info = register_project(
298 |     path="/path/to/project", 
299 |     name="my-project", 
300 |     description="Description"
301 | )
302 | 
303 | # List projects
304 | projects = list_projects()
305 | 
306 | # Get configuration
307 | config = get_config()
308 | 
309 | # Access components through dependency injection
310 | from mcp_server_tree_sitter.di import get_container
311 | container = get_container()
312 | project_registry = container.project_registry
313 | language_registry = container.language_registry
314 | ```
315 | 
316 | ## Configuration
317 | 
318 | Create a YAML configuration file:
319 | 
320 | ```yaml
321 | cache:
322 |   enabled: true                # Enable/disable caching (default: true)
323 |   max_size_mb: 100             # Maximum cache size in MB (default: 100)
324 |   ttl_seconds: 300             # Cache entry time-to-live in seconds (default: 300)
325 | 
326 | security:
327 |   max_file_size_mb: 5          # Maximum file size to process in MB (default: 5)
328 |   excluded_dirs:               # Directories to exclude from processing
329 |     - .git
330 |     - node_modules
331 |     - __pycache__
332 |   allowed_extensions:          # Optional list of allowed file extensions
333 |     # - py
334 |     # - js
335 |     # Leave empty or omit for all extensions
336 | 
337 | language:
338 |   default_max_depth: 5         # Default max depth for AST traversal (default: 5)
339 |   preferred_languages:         # List of languages to pre-load at startup for faster performance
340 |     - python                   # Pre-loading reduces latency for first operations
341 |     - javascript
342 | 
343 | log_level: INFO                # Logging level (DEBUG, INFO, WARNING, ERROR)
344 | max_results_default: 100       # Default maximum results for search operations
345 | ```
346 | 
347 | Load it with:
348 | 
349 | ```
350 | configure(config_path="/path/to/config.yaml")
351 | ```
352 | 
353 | ### Logging Configuration
354 | 
355 | The server's logging verbosity can be controlled using environment variables:
356 | 
357 | ```bash
358 | # Enable detailed debug logging
359 | export MCP_TS_LOG_LEVEL=DEBUG
360 | 
361 | # Use normal informational logging (default)
362 | export MCP_TS_LOG_LEVEL=INFO
363 | 
364 | # Only show warning and error messages
365 | export MCP_TS_LOG_LEVEL=WARNING
366 | ```
367 | 
368 | For comprehensive information about logging configuration, please refer to the [logging documentation](docs/logging.md). For details on the command-line interface, see the [CLI documentation](docs/cli.md).
369 | 
370 | ### About preferred_languages
371 | 
372 | The `preferred_languages` setting controls which language parsers are pre-loaded at server startup rather than on-demand. This provides several benefits:
373 | 
374 | - **Faster initial analysis**: No delay when first analyzing a file of a pre-loaded language
375 | - **Early error detection**: Issues with parsers are discovered at startup, not during use
376 | - **Predictable memory allocation**: Memory for frequently used parsers is allocated upfront
377 | 
378 | By default, all parsers are loaded on-demand when first needed. For optimal performance, specify the languages you use most frequently in your projects.
379 | 
380 | You can also configure specific settings:
381 | 
382 | ```
383 | configure(cache_enabled=True, max_file_size_mb=10, log_level="DEBUG")
384 | ```
385 | 
386 | Or use environment variables:
387 | 
388 | ```bash
389 | export MCP_TS_CACHE_MAX_SIZE_MB=256
390 | export MCP_TS_LOG_LEVEL=DEBUG
391 | export MCP_TS_CONFIG_PATH=/path/to/config.yaml
392 | ```
393 | 
394 | Environment variables use the format `MCP_TS_SECTION_SETTING` (e.g., `MCP_TS_CACHE_MAX_SIZE_MB`) for section settings, or `MCP_TS_SETTING` (e.g., `MCP_TS_LOG_LEVEL`) for top-level settings.
395 | 
396 | Configuration values are applied in this order of precedence:
397 | 1. Environment variables (highest)
398 | 2. Values set via `configure()` calls
399 | 3. YAML configuration file
400 | 4. Default values (lowest)
401 | 
402 | The server will look for configuration in:
403 | 1. Path specified in `configure()` call
404 | 2. Path specified by `MCP_TS_CONFIG_PATH` environment variable
405 | 3. Default location: `~/.config/tree-sitter/config.yaml`
406 | 
407 | ## For Developers
408 | 
409 | ### Diagnostic Capabilities
410 | 
411 | The MCP Tree-sitter Server includes a diagnostic framework to help identify and fix issues:
412 | 
413 | ```bash
414 | # Run diagnostic tests
415 | make test-diagnostics
416 | 
417 | # CI-friendly version (won't fail the build on diagnostic issues)
418 | make test-diagnostics-ci
419 | ```
420 | 
421 | Diagnostic tests provide detailed information about the server's behavior and can help isolate specific issues. For more information about the diagnostic framework, please see the [diagnostics documentation](docs/diagnostics.md).
422 | 
423 | ### Type Safety Considerations
424 | 
425 | The MCP Tree-sitter Server maintains type safety when interfacing with tree-sitter libraries through careful design patterns and protocols. If you're extending the codebase, please review the [type safety guide](docs/tree-sitter-type-safety.md) for important information about handling tree-sitter API variations.
426 | 
427 | ## Available Resources
428 | 
429 | The server provides the following MCP resources:
430 | 
431 | - `project://{project}/files` - List all files in a project
432 | - `project://{project}/files/{pattern}` - List files matching a pattern
433 | - `project://{project}/file/{path}` - Get file content
434 | - `project://{project}/file/{path}/lines/{start}-{end}` - Get specific lines from a file
435 | - `project://{project}/ast/{path}` - Get the AST for a file
436 | - `project://{project}/ast/{path}/depth/{depth}` - Get the AST with custom depth
437 | 
438 | ## Available Tools
439 | 
440 | The server provides tools for:
441 | 
442 | - Project management: `register_project_tool`, `list_projects_tool`, `remove_project_tool`
443 | - Language management: `list_languages`, `check_language_available`
444 | - File operations: `list_files`, `get_file`, `get_file_metadata`
445 | - AST analysis: `get_ast`, `get_node_at_position`
446 | - Code search: `find_text`, `run_query`
447 | - Symbol extraction: `get_symbols`, `find_usage`
448 | - Project analysis: `analyze_project`, `get_dependencies`, `analyze_complexity`
449 | - Query building: `get_query_template_tool`, `list_query_templates_tool`, `build_query`, `adapt_query`, `get_node_types`
450 | - Similar code detection: `find_similar_code`
451 | - Cache management: `clear_cache`
452 | - Configuration diagnostics: `diagnose_config`
453 | 
454 | See [FEATURES.md](FEATURES.md) for detailed information about each tool's implementation status, dependencies, and usage examples.
455 | 
456 | ## Available Prompts
457 | 
458 | The server provides the following MCP prompts:
459 | 
460 | - `code_review` - Create a prompt for reviewing code
461 | - `explain_code` - Create a prompt for explaining code
462 | - `explain_tree_sitter_query` - Explain tree-sitter query syntax
463 | - `suggest_improvements` - Create a prompt for suggesting code improvements
464 | - `project_overview` - Create a prompt for a project overview analysis
465 | 
466 | ## License
467 | 
468 | MIT
```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Contributing to MCP Tree-sitter Server
  2 | 
  3 | Thank you for your interest in contributing to MCP Tree-sitter Server! This guide will help you understand our development process and coding standards.
  4 | 
  5 | ## Development Setup
  6 | 
  7 | 1. Clone the repository:
  8 |    ```bash
  9 |    git clone https://github.com/organization/mcp-server-tree-sitter.git
 10 |    cd mcp-server-tree-sitter
 11 |    ```
 12 | 
 13 | 2. Install with development dependencies:
 14 |    ```bash
 15 |    make install-dev
 16 |    ```
 17 | 
 18 | 3. Install language parsers (optional):
 19 |    ```bash
 20 |    make install-languages
 21 |    ```
 22 | 
 23 | ## Code Style and Standards
 24 | 
 25 | We follow a strict set of coding standards to maintain consistency throughout the codebase:
 26 | 
 27 | ### Python Style
 28 | 
 29 | - We use [Black](https://black.readthedocs.io/) for code formatting with a line length of 88 characters
 30 | - We use [Ruff](https://github.com/charliermarsh/ruff) for linting
 31 | - We use [MyPy](https://mypy.readthedocs.io/) for static type checking
 32 | 
 33 | ### Exception Handling
 34 | 
 35 | - Use specific exception types rather than catching generic exceptions when possible
 36 | - When re-raising exceptions, use the `from` clause to preserve the stack trace:
 37 |   ```python
 38 |   try:
 39 |       # Some code
 40 |   except SomeError as e:
 41 |       raise CustomError("Meaningful message") from e
 42 |   ```
 43 | 
 44 | ### Testing
 45 | 
 46 | - Write tests for all new functionality
 47 | - Run tests before submitting:
 48 |   ```bash
 49 |   make test
 50 |   ```
 51 | 
 52 | ### Documentation
 53 | 
 54 | - Document all functions, classes, and modules using docstrings
 55 | - Follow the Google Python Style Guide for docstrings
 56 | - Include type hints for all function parameters and return values
 57 | 
 58 | ## Development Workflow
 59 | 
 60 | 1. Create a branch for your feature or bugfix:
 61 |    ```bash
 62 |    git checkout -b feature/your-feature-name
 63 |    ```
 64 | 
 65 | 2. Make your changes and ensure they pass linting and tests:
 66 |    ```bash
 67 |    make format
 68 |    make lint
 69 |    make test
 70 |    ```
 71 | 
 72 | 3. Commit your changes with a clear message describing the change
 73 | 
 74 | 4. Submit a pull request to the main repository
 75 | 
 76 | ## Running the Server
 77 | 
 78 | You can run the server in different modes:
 79 | 
 80 | - For development and testing:
 81 |   ```bash
 82 |   make mcp-dev
 83 |   ```
 84 | 
 85 | - For direct execution:
 86 |   ```bash
 87 |   make mcp-run
 88 |   ```
 89 | 
 90 | - To install in Claude Desktop:
 91 |   ```bash
 92 |   make mcp-install
 93 |   ```
 94 | 
 95 | ## Project Architecture
 96 | 
 97 | The project follows a modular architecture:
 98 | 
 99 | - `config.py` - Configuration management
100 | - `language/` - Tree-sitter language handling
101 | - `models/` - Data models for AST and projects
102 | - `cache/` - Caching mechanisms
103 | - `resources/` - MCP resources (files, AST)
104 | - `tools/` - MCP tools (search, analysis, etc.)
105 | - `utils/` - Utility functions
106 | - `prompts/` - MCP prompts
107 | - `server.py` - FastMCP server implementation
108 | 
109 | ## Seeking Help
110 | 
111 | If you have questions or need help, please open an issue or contact the maintainers.
112 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/tools/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """MCP tool components."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/prompts/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """MCP prompt components."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/models/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Data models for MCP server."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/cache/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Cache components for MCP server."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Utility functions for MCP server."""
2 | 
```

--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Test package for mcp-server-tree-sitter."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Language handling components for MCP server."""
2 | 
```

--------------------------------------------------------------------------------
/tests/test_diagnostics/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Pytest-based diagnostic tests for mcp-server-tree-sitter."""
2 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/capabilities/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """MCP capability declarations."""
2 | 
3 | from .server_capabilities import register_capabilities
4 | 
5 | __all__ = ["register_capabilities"]
6 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/context/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Context handling utilities for MCP operations."""
2 | 
3 | from .mcp_context import MCPContext, ProgressScope
4 | 
5 | __all__ = ["MCPContext", "ProgressScope"]
6 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/testing/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """Testing utilities for mcp-server-tree-sitter."""
2 | 
3 | from .pytest_diagnostic import DiagnosticData, diagnostic
4 | 
5 | __all__ = ["DiagnosticData", "diagnostic"]
6 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/bootstrap/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """Bootstrap package for early initialization dependencies.
 2 | 
 3 | This package contains modules that should be imported and initialized before
 4 | any other modules in the project to ensure proper setup of core services.
 5 | """
 6 | 
 7 | # Import logging bootstrap module to ensure it's available
 8 | from . import logging_bootstrap
 9 | 
10 | # Export key functions for convenience
11 | from .logging_bootstrap import get_log_level_from_env, get_logger, update_log_levels
12 | 
13 | __all__ = ["get_logger", "update_log_levels", "get_log_level_from_env", "logging_bootstrap"]
14 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """MCP Server for Tree-sitter - Code analysis capabilities using tree-sitter.
 2 | 
 3 | This module provides a Model Context Protocol server that gives LLMs like Claude
 4 | intelligent access to codebases with appropriate context management.
 5 | """
 6 | 
 7 | # Import bootstrap package first to ensure core services are set up
 8 | # before any other modules are imported
 9 | from . import bootstrap as bootstrap  # noqa: F401 - Import needed for initialization
10 | 
11 | # Logging is now configured via the bootstrap.logging_bootstrap module
12 | # The bootstrap module automatically calls configure_root_logger() when imported
13 | 
14 | __version__ = "0.1.0"
15 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """Language-specific query templates collection."""
 2 | 
 3 | from typing import Dict
 4 | 
 5 | from . import (
 6 |     apl,
 7 |     c,
 8 |     cpp,
 9 |     go,
10 |     java,
11 |     javascript,
12 |     julia,
13 |     kotlin,
14 |     python,
15 |     rust,
16 |     swift,
17 |     typescript,
18 | )
19 | 
20 | # Combine all language templates
21 | QUERY_TEMPLATES: Dict[str, Dict[str, str]] = {
22 |     "python": python.TEMPLATES,
23 |     "javascript": javascript.TEMPLATES,
24 |     "typescript": typescript.TEMPLATES,
25 |     "go": go.TEMPLATES,
26 |     "rust": rust.TEMPLATES,
27 |     "c": c.TEMPLATES,
28 |     "cpp": cpp.TEMPLATES,
29 |     "swift": swift.TEMPLATES,
30 |     "java": java.TEMPLATES,
31 |     "kotlin": kotlin.TEMPLATES,
32 |     "julia": julia.TEMPLATES,
33 |     "apl": apl.TEMPLATES,
34 | }
35 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/apl.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for APL language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_definition
 6 |             name: (identifier) @function.name
 7 |             body: (block) @function.body) @function.def
 8 |     """,
 9 |     "namespaces": """
10 |         (namespace_declaration
11 |             name: (identifier) @namespace.name) @namespace.def
12 |     """,
13 |     "variables": """
14 |         (assignment
15 |             left: (identifier) @variable.name) @variable.def
16 |     """,
17 |     "imports": """
18 |         (import_statement
19 |             module: (identifier) @import.module) @import
20 |     """,
21 |     "operators": """
22 |         (operator_definition
23 |             operator: (_) @operator.sym
24 |             body: (block) @operator.body) @operator.def
25 |     """,
26 |     "classes": """
27 |         (class_definition
28 |             name: (identifier) @class.name
29 |             body: (block) @class.body) @class.def
30 |     """,
31 | }
32 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/query_templates.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for common code patterns by language."""
 2 | 
 3 | from typing import Any, Dict, Optional
 4 | 
 5 | from .templates import QUERY_TEMPLATES
 6 | 
 7 | 
 8 | def get_query_template(language: str, template_name: str) -> Optional[str]:
 9 |     """
10 |     Get a query template for a language.
11 | 
12 |     Args:
13 |         language: Language identifier
14 |         template_name: Template name
15 | 
16 |     Returns:
17 |         Query string or None if not found
18 |     """
19 |     language_templates = QUERY_TEMPLATES.get(language)
20 |     if language_templates:
21 |         return language_templates.get(template_name)
22 |     return None
23 | 
24 | 
25 | def list_query_templates(language: Optional[str] = None) -> Dict[str, Any]:
26 |     """
27 |     List available query templates.
28 | 
29 |     Args:
30 |         language: Optional language to filter by
31 | 
32 |     Returns:
33 |         Dictionary of templates by language
34 |     """
35 |     if language:
36 |         return {language: QUERY_TEMPLATES.get(language, {})}
37 |     return QUERY_TEMPLATES
38 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/logging_config.py:
--------------------------------------------------------------------------------

```python
 1 | """Logging configuration for MCP Tree-sitter Server.
 2 | 
 3 | This module is maintained for backwards compatibility.
 4 | All functionality has been moved to the bootstrap.logging_bootstrap module,
 5 | which is the canonical source for logging configuration.
 6 | 
 7 | All imports from this module should be updated to use:
 8 |     from mcp_server_tree_sitter.bootstrap import get_logger, update_log_levels
 9 | """
10 | 
11 | # Import the bootstrap module's logging components to maintain backwards compatibility
12 | from .bootstrap.logging_bootstrap import (
13 |     LOG_LEVEL_MAP,
14 |     configure_root_logger,
15 |     get_log_level_from_env,
16 |     get_logger,
17 |     update_log_levels,
18 | )
19 | 
20 | # Re-export all the functions and constants for backwards compatibility
21 | __all__ = ["LOG_LEVEL_MAP", "configure_root_logger", "get_log_level_from_env", "get_logger", "update_log_levels"]
22 | 
23 | # The bootstrap module already calls configure_root_logger() when imported,
24 | # so we don't need to call it again here.
25 | 
```

--------------------------------------------------------------------------------
/scripts/implementation-search.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | # implementation-search.sh - Script to spot check implementation patterns
 3 | 
 4 | # Enable strict mode
 5 | set -euo pipefail
 6 | 
 7 | # Check if search term is provided
 8 | if [ $# -eq 0 ]; then
 9 |     echo "Usage: $0 <search_term>"
10 |     exit 1
11 | fi
12 | 
13 | # Directories to exclude
14 | EXCLUDE_DIRS=(
15 |     ".venv"
16 |     ".git"
17 |     "./diagnostic_results"
18 |     "./.pytest_cache"
19 |     "./.ruff_cache"
20 |     "./.mypy_cache"
21 |     "./tests/__pycache__"
22 |     "./__pycache__"
23 |     "./src/mcp_server_tree_sitter/__pycache__"
24 |     "./src/*/bootstrap/__pycache__"
25 |     "./src/*/__pycache__"
26 | )
27 | 
28 | # Files to exclude
29 | EXCLUDE_FILES=(
30 |     "./.gitignore"
31 |     "./TODO.md"
32 |     "./FEATURES.md"
33 | )
34 | 
35 | # Build exclude arguments for grep
36 | EXCLUDE_ARGS=""
37 | for dir in "${EXCLUDE_DIRS[@]}"; do
38 |     EXCLUDE_ARGS+="--exclude-dir=${dir} "
39 | done
40 | 
41 | for file in "${EXCLUDE_FILES[@]}"; do
42 |     EXCLUDE_ARGS+="--exclude=${file} "
43 | done
44 | 
45 | # Run grep with all exclusions
46 | grep -r "${1}" . ${EXCLUDE_ARGS} --binary-files=without-match
47 | 
```

--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------

```python
 1 | """Pytest configuration for mcp-server-tree-sitter tests."""
 2 | 
 3 | import pytest
 4 | 
 5 | # Import and register the diagnostic plugin
 6 | pytest_plugins = ["mcp_server_tree_sitter.testing.pytest_diagnostic"]
 7 | 
 8 | 
 9 | @pytest.fixture(autouse=True, scope="function")
10 | def reset_project_registry():
11 |     """Reset the project registry between tests.
12 | 
13 |     This prevents tests from interfering with each other when using the
14 |     project registry, which is a singleton that persists across tests.
15 |     """
16 |     # Import here to avoid circular imports
17 |     from mcp_server_tree_sitter.di import get_container
18 | 
19 |     # Get registry through DI container
20 |     container = get_container()
21 |     registry = container.project_registry
22 | 
23 |     # Store original projects to restore after test
24 |     original_projects = dict(registry._projects)
25 | 
26 |     # Clear for this test
27 |     registry._projects.clear()
28 | 
29 |     yield
30 | 
31 |     # Restore original projects
32 |     registry._projects.clear()
33 |     registry._projects.update(original_projects)
34 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/c.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for C language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_definition
 6 |             declarator: (function_declarator
 7 |                 declarator: (identifier) @function.name)) @function.def
 8 | 
 9 |         (declaration
10 |             declarator: (function_declarator
11 |                 declarator: (identifier) @function.name)) @function.decl
12 |     """,
13 |     "structs": """
14 |         (struct_specifier
15 |             name: (type_identifier) @struct.name) @struct.def
16 | 
17 |         (union_specifier
18 |             name: (type_identifier) @union.name) @union.def
19 | 
20 |         (enum_specifier
21 |             name: (type_identifier) @enum.name) @enum.def
22 |     """,
23 |     "imports": """
24 |         (preproc_include) @import
25 | 
26 |         (preproc_include
27 |             path: (string_literal) @import.system) @import.system
28 | 
29 |         (preproc_include
30 |             path: (system_lib_string) @import.system) @import.system
31 |     """,
32 |     "macros": """
33 |         (preproc_function_def
34 |             name: (identifier) @macro.name) @macro.def
35 |     """,
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/exceptions.py:
--------------------------------------------------------------------------------

```python
 1 | """Exception classes for mcp-server-tree-sitter."""
 2 | 
 3 | 
 4 | class MCPTreeSitterError(Exception):
 5 |     """Base exception for mcp-server-tree-sitter."""
 6 | 
 7 |     pass
 8 | 
 9 | 
10 | class LanguageError(MCPTreeSitterError):
11 |     """Errors related to tree-sitter languages."""
12 | 
13 |     pass
14 | 
15 | 
16 | class LanguageNotFoundError(LanguageError):
17 |     """Raised when a language parser is not available."""
18 | 
19 |     pass
20 | 
21 | 
22 | class LanguageInstallError(LanguageError):
23 |     """Raised when language installation fails."""
24 | 
25 |     pass
26 | 
27 | 
28 | class ParsingError(MCPTreeSitterError):
29 |     """Errors during parsing."""
30 | 
31 |     pass
32 | 
33 | 
34 | class ProjectError(MCPTreeSitterError):
35 |     """Errors related to project management."""
36 | 
37 |     pass
38 | 
39 | 
40 | class FileAccessError(MCPTreeSitterError):
41 |     """Errors accessing project files."""
42 | 
43 |     pass
44 | 
45 | 
46 | class QueryError(MCPTreeSitterError):
47 |     """Errors related to tree-sitter queries."""
48 | 
49 |     pass
50 | 
51 | 
52 | class SecurityError(MCPTreeSitterError):
53 |     """Security-related errors."""
54 | 
55 |     pass
56 | 
57 | 
58 | class CacheError(MCPTreeSitterError):
59 |     """Errors related to caching."""
60 | 
61 |     pass
62 | 
```

--------------------------------------------------------------------------------
/tests/test_di.py:
--------------------------------------------------------------------------------

```python
 1 | """Tests for the dependency injection container."""
 2 | 
 3 | from mcp_server_tree_sitter.di import get_container
 4 | 
 5 | 
 6 | def test_container_singleton():
 7 |     """Test that get_container returns the same instance each time."""
 8 |     container1 = get_container()
 9 |     container2 = get_container()
10 |     assert container1 is container2
11 | 
12 | 
13 | def test_register_custom_dependency():
14 |     """Test registering and retrieving a custom dependency."""
15 |     container = get_container()
16 | 
17 |     # Register a custom dependency
18 |     test_value = {"test": "value"}
19 |     container.register_dependency("test_dependency", test_value)
20 | 
21 |     # Retrieve it
22 |     retrieved = container.get_dependency("test_dependency")
23 |     assert retrieved is test_value
24 | 
25 | 
26 | def test_core_dependencies_initialized():
27 |     """Test that core dependencies are automatically initialized."""
28 |     container = get_container()
29 | 
30 |     assert container.config_manager is not None
31 |     assert container.project_registry is not None
32 |     assert container.language_registry is not None
33 |     assert container.tree_cache is not None
34 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/javascript.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for JavaScript."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_declaration
 6 |             name: (identifier) @function.name
 7 |             parameters: (formal_parameters) @function.params
 8 |             body: (statement_block) @function.body) @function.def
 9 | 
10 |         (arrow_function
11 |             parameters: (formal_parameters) @function.params
12 |             body: (_) @function.body) @function.def
13 |     """,
14 |     "classes": """
15 |         (class_declaration
16 |             name: (identifier) @class.name
17 |             body: (class_body) @class.body) @class.def
18 |     """,
19 |     "imports": """
20 |         (import_statement) @import
21 | 
22 |         (import_statement
23 |             source: (string) @import.source
24 |             specifier: (_) @import.specifier) @import.full
25 |     """,
26 |     "function_calls": """
27 |         (call_expression
28 |             function: (identifier) @call.function
29 |             arguments: (arguments) @call.args) @call
30 |     """,
31 |     "assignments": """
32 |         (variable_declarator
33 |             name: (_) @assign.target
34 |             value: (_) @assign.value) @assign
35 |     """,
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/rust.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Rust."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_item
 6 |             name: (identifier) @function.name
 7 |             parameters: (parameters) @function.params
 8 |             body: (block) @function.body) @function.def
 9 |     """,
10 |     "structs": """
11 |         (struct_item
12 |             name: (type_identifier) @struct.name
13 |             body: (field_declaration_list) @struct.body) @struct.def
14 |     """,
15 |     "enums": """
16 |         (enum_item
17 |             name: (type_identifier) @enum.name
18 |             body: (enum_variant_list) @enum.body) @enum.def
19 |     """,
20 |     "imports": """
21 |         (use_declaration) @import
22 | 
23 |         (use_declaration
24 |             (identifier) @import.name) @import.direct
25 | 
26 |         (use_declaration
27 |             (scoped_identifier
28 |                 path: (_) @import.path
29 |                 name: (identifier) @import.name)) @import.scoped
30 | 
31 |         (use_declaration
32 |             (scoped_use_list
33 |                 path: (_) @import.path)) @import.list
34 |     """,
35 |     "traits": """
36 |         (trait_item
37 |             name: (type_identifier) @trait.name) @trait.def
38 |     """,
39 |     "impls": """
40 |         (impl_item
41 |             trait: (_)? @impl.trait
42 |             type: (_) @impl.type) @impl.def
43 |     """,
44 | }
45 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/python.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Python."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_definition
 6 |             name: (identifier) @function.name
 7 |             parameters: (parameters) @function.params
 8 |             body: (block) @function.body) @function.def
 9 |     """,
10 |     "classes": """
11 |         (class_definition
12 |             name: (identifier) @class.name
13 |             body: (block) @class.body) @class.def
14 |     """,
15 |     "imports": """
16 |         (import_statement
17 |             name: (dotted_name) @import.module) @import
18 | 
19 |         (import_from_statement
20 |             module_name: (dotted_name) @import.from
21 |             name: (dotted_name) @import.item) @import
22 | 
23 |         ;; Handle aliased imports with 'as' keyword
24 |         (import_from_statement
25 |             module_name: (dotted_name) @import.from
26 |             name: (aliased_import
27 |                 name: (dotted_name) @import.item
28 |                 alias: (identifier) @import.alias)) @import
29 |     """,
30 |     "function_calls": """
31 |         (call
32 |             function: (identifier) @call.function
33 |             arguments: (argument_list) @call.args) @call
34 |     """,
35 |     "assignments": """
36 |         (assignment
37 |             left: (_) @assign.target
38 |             right: (_) @assign.value) @assign
39 |     """,
40 | }
41 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/go.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Go."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_declaration
 6 |             name: (identifier) @function.name
 7 |             parameters: (parameter_list) @function.params
 8 |             body: (block) @function.body) @function.def
 9 | 
10 |         (method_declaration
11 |             name: (field_identifier) @method.name
12 |             parameters: (parameter_list) @method.params
13 |             body: (block) @method.body) @method.def
14 |     """,
15 |     "structs": """
16 |         (type_declaration
17 |             (type_spec
18 |                 name: (type_identifier) @struct.name
19 |                 type: (struct_type) @struct.body)) @struct.def
20 | 
21 |         (type_declaration
22 |             (type_spec
23 |                 name: (type_identifier) @type.name
24 |                 type: (_) @type.body)) @type.def
25 |     """,
26 |     "imports": """
27 |         (import_declaration) @import
28 | 
29 |         (import_declaration
30 |             (import_spec_list
31 |                 (import_spec) @import.spec)) @import.list
32 | 
33 |         (import_declaration
34 |             (import_spec_list
35 |                 (import_spec
36 |                     path: (_) @import.path))) @import.path_list
37 | 
38 |         (import_declaration
39 |             (import_spec
40 |                 path: (_) @import.path)) @import.single
41 |     """,
42 |     "interfaces": """
43 |         (type_declaration
44 |             (type_spec
45 |                 name: (type_identifier) @interface.name
46 |                 type: (interface_type) @interface.body)) @interface.def
47 |     """,
48 | }
49 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/cpp.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for C++ language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_definition
 6 |             declarator: (function_declarator
 7 |                 declarator: (identifier) @function.name)) @function.def
 8 | 
 9 |         (declaration
10 |             declarator: (function_declarator
11 |                 declarator: (identifier) @function.name)) @function.decl
12 | 
13 |         (method_definition
14 |             declarator: (function_declarator
15 |                 declarator: (field_identifier) @method.name)) @method.def
16 |     """,
17 |     "classes": """
18 |         (class_specifier
19 |             name: (type_identifier) @class.name) @class.def
20 |     """,
21 |     "structs": """
22 |         (struct_specifier
23 |             name: (type_identifier) @struct.name) @struct.def
24 | 
25 |         (union_specifier
26 |             name: (type_identifier) @union.name) @union.def
27 | 
28 |         (enum_specifier
29 |             name: (type_identifier) @enum.name) @enum.def
30 |     """,
31 |     "imports": """
32 |         (preproc_include) @import
33 | 
34 |         (preproc_include
35 |             path: (string_literal) @import.path) @import.user
36 | 
37 |         (preproc_include
38 |             path: (system_lib_string) @import.path) @import.system
39 | 
40 |         (namespace_definition
41 |             name: (namespace_identifier) @import.namespace) @import.namespace_def
42 |     """,
43 |     "templates": """
44 |         (template_declaration) @template.def
45 | 
46 |         (template_declaration
47 |             declaration: (class_specifier
48 |                 name: (type_identifier) @template.class)) @template.class_def
49 |     """,
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/java.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Java language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (method_declaration
 6 |             name: (identifier) @function.name
 7 |             parameters: (formal_parameters) @function.params
 8 |             body: (block) @function.body) @function.def
 9 | 
10 |         (constructor_declaration
11 |             name: (identifier) @constructor.name
12 |             parameters: (formal_parameters) @constructor.params
13 |             body: (block) @constructor.body) @constructor.def
14 |     """,
15 |     "classes": """
16 |         (class_declaration
17 |             name: (identifier) @class.name
18 |             body: (class_body) @class.body) @class.def
19 |     """,
20 |     "interfaces": """
21 |         (interface_declaration
22 |             name: (identifier) @interface.name
23 |             body: (class_body) @interface.body) @interface.def
24 |     """,
25 |     "imports": """
26 |         (import_declaration) @import
27 | 
28 |         (import_declaration
29 |             name: (qualified_name) @import.name) @import.qualified
30 | 
31 |         (import_declaration
32 |             name: (qualified_name
33 |                 name: (identifier) @import.class)) @import.class
34 | 
35 |         (import_declaration
36 |             asterisk: "*") @import.wildcard
37 |     """,
38 |     "annotations": """
39 |         (annotation
40 |             name: (identifier) @annotation.name) @annotation
41 | 
42 |         (annotation_type_declaration
43 |             name: (identifier) @annotation.type_name) @annotation.type
44 |     """,
45 |     "enums": """
46 |         (enum_declaration
47 |             name: (identifier) @enum.name
48 |             body: (enum_body) @enum.body) @enum.def
49 |     """,
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/julia.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Julia language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_definition
 6 |             name: (identifier) @function.name) @function.def
 7 | 
 8 |         (function_definition
 9 |             name: (identifier) @function.name
10 |             parameters: (parameter_list) @function.params
11 |             body: (block) @function.body) @function.def
12 | 
13 |         (short_function_definition
14 |             name: (identifier) @function.name) @function.short_def
15 |     """,
16 |     "modules": """
17 |         (module_definition
18 |             name: (identifier) @module.name
19 |             body: (block) @module.body) @module.def
20 |     """,
21 |     "structs": """
22 |         (struct_definition
23 |             name: (identifier) @struct.name
24 |             body: (block) @struct.body) @struct.def
25 | 
26 |         (mutable_struct_definition
27 |             name: (identifier) @struct.name
28 |             body: (block) @struct.body) @struct.mutable_def
29 |     """,
30 |     "imports": """
31 |         (import_statement) @import
32 | 
33 |         (import_statement
34 |             name: (identifier) @import.name) @import.simple
35 | 
36 |         (using_statement) @using
37 | 
38 |         (using_statement
39 |             name: (identifier) @using.name) @using.simple
40 | 
41 |         (import_statement
42 |             name: (dot_expression) @import.qualified) @import.qualified
43 |     """,
44 |     "macros": """
45 |         (macro_definition
46 |             name: (identifier) @macro.name
47 |             body: (block) @macro.body) @macro.def
48 |     """,
49 |     "abstractTypes": """
50 |         (abstract_definition
51 |             name: (identifier) @abstract.name) @abstract.def
52 |     """,
53 | }
54 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/swift.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Swift language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_declaration
 6 |             name: (identifier) @function.name) @function.def
 7 | 
 8 |         (function_declaration
 9 |             name: (identifier) @function.name
10 |             body: (code_block) @function.body) @function.def
11 |     """,
12 |     "classes": """
13 |         (class_declaration
14 |             name: (type_identifier) @class.name) @class.def
15 | 
16 |         (class_declaration
17 |             name: (type_identifier) @class.name
18 |             body: (class_body) @class.body) @class.def
19 |     """,
20 |     "structs": """
21 |         (struct_declaration
22 |             name: (type_identifier) @struct.name) @struct.def
23 | 
24 |         (struct_declaration
25 |             name: (type_identifier) @struct.name
26 |             body: (struct_body) @struct.body) @struct.def
27 |     """,
28 |     "imports": """
29 |         (import_declaration) @import
30 | 
31 |         (import_declaration
32 |             path: (identifier) @import.path) @import.simple
33 | 
34 |         (import_declaration
35 |             path: (_) @import.path) @import.complex
36 |     """,
37 |     "protocols": """
38 |         (protocol_declaration
39 |             name: (type_identifier) @protocol.name) @protocol.def
40 | 
41 |         (protocol_declaration
42 |             name: (type_identifier) @protocol.name
43 |             body: (protocol_body) @protocol.body) @protocol.def
44 |     """,
45 |     "extensions": """
46 |         (extension_declaration
47 |             name: (type_identifier) @extension.name) @extension.def
48 | 
49 |         (extension_declaration
50 |             name: (type_identifier) @extension.name
51 |             body: (extension_body) @extension.body) @extension.def
52 |     """,
53 | }
54 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/typescript.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for TypeScript."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_declaration
 6 |             name: (identifier) @function.name
 7 |             parameters: (formal_parameters) @function.params
 8 |             body: (statement_block) @function.body) @function.def
 9 | 
10 |         (arrow_function
11 |             parameters: (formal_parameters) @function.params
12 |             body: (_) @function.body) @function.def
13 | 
14 |         (method_definition
15 |             name: (property_identifier) @method.name
16 |             parameters: (formal_parameters) @method.params
17 |             body: (statement_block) @method.body) @method.def
18 |     """,
19 |     "classes": """
20 |         (class_declaration
21 |             name: (type_identifier) @class.name
22 |             body: (class_body) @class.body) @class.def
23 |     """,
24 |     "interfaces": """
25 |         (interface_declaration
26 |             name: (type_identifier) @interface.name
27 |             body: (object_type) @interface.body) @interface.def
28 | 
29 |         (type_alias_declaration
30 |             name: (type_identifier) @alias.name
31 |             value: (_) @alias.value) @alias.def
32 |     """,
33 |     "imports": """
34 |         (import_statement) @import
35 | 
36 |         (import_statement
37 |             source: (string) @import.source) @import.source_only
38 | 
39 |         (import_statement
40 |             source: (string) @import.source
41 |             specifier: (named_imports
42 |                 (import_specifier
43 |                     name: (identifier) @import.name))) @import.named
44 | 
45 |         (import_statement
46 |             source: (string) @import.source
47 |             specifier: (namespace_import
48 |                 name: (identifier) @import.namespace)) @import.namespace
49 |     """,
50 | }
51 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/language/templates/kotlin.py:
--------------------------------------------------------------------------------

```python
 1 | """Query templates for Kotlin language."""
 2 | 
 3 | TEMPLATES = {
 4 |     "functions": """
 5 |         (function_declaration
 6 |             name: (simple_identifier) @function.name) @function.def
 7 | 
 8 |         (function_declaration
 9 |             name: (simple_identifier) @function.name
10 |             function_body: (function_body) @function.body) @function.def
11 |     """,
12 |     "classes": """
13 |         (class_declaration
14 |             name: (simple_identifier) @class.name) @class.def
15 | 
16 |         (class_declaration
17 |             name: (simple_identifier) @class.name
18 |             class_body: (class_body) @class.body) @class.def
19 |     """,
20 |     "interfaces": """
21 |         (interface_declaration
22 |             name: (simple_identifier) @interface.name) @interface.def
23 | 
24 |         (interface_declaration
25 |             name: (simple_identifier) @interface.name
26 |             class_body: (class_body) @interface.body) @interface.def
27 |     """,
28 |     "imports": """
29 |         (import_header) @import
30 | 
31 |         (import_header
32 |             identifier: (identifier) @import.id) @import.simple
33 | 
34 |         (import_header
35 |             identifier: (dot_qualified_expression) @import.qualified) @import.qualified
36 | 
37 |         (import_header
38 |             import_alias: (import_alias
39 |                 name: (simple_identifier) @import.alias)) @import.aliased
40 |     """,
41 |     "properties": """
42 |         (property_declaration
43 |             variable_declaration: (variable_declaration
44 |                 simple_identifier: (simple_identifier) @property.name)) @property.def
45 |     """,
46 |     "dataClasses": """
47 |         (class_declaration
48 |             type: (type_modifiers
49 |                 (type_modifier
50 |                     "data" @data_class.modifier))
51 |             name: (simple_identifier) @data_class.name) @data_class.def
52 |     """,
53 | }
54 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/di.py:
--------------------------------------------------------------------------------

```python
 1 | """Dependency injection container for MCP Tree-sitter Server.
 2 | 
 3 | This module provides a central container for managing all application dependencies,
 4 | replacing the global variables and singletons previously used throughout the codebase.
 5 | """
 6 | 
 7 | from typing import Any, Dict
 8 | 
 9 | # Import logging from bootstrap package
10 | from .bootstrap import get_logger
11 | from .cache.parser_cache import TreeCache
12 | from .config import ConfigurationManager, ServerConfig
13 | from .language.registry import LanguageRegistry
14 | from .models.project import ProjectRegistry
15 | 
16 | logger = get_logger(__name__)
17 | 
18 | 
19 | class DependencyContainer:
20 |     """Container for all application dependencies."""
21 | 
22 |     def __init__(self) -> None:
23 |         """Initialize container with all core dependencies."""
24 |         logger.debug("Initializing dependency container")
25 | 
26 |         # Create core dependencies
27 |         self.config_manager = ConfigurationManager()
28 |         self._config = self.config_manager.get_config()
29 |         self.project_registry = ProjectRegistry()
30 |         self.language_registry = LanguageRegistry()
31 |         self.tree_cache = TreeCache(
32 |             max_size_mb=self._config.cache.max_size_mb, ttl_seconds=self._config.cache.ttl_seconds
33 |         )
34 | 
35 |         # Storage for any additional dependencies
36 |         self._additional: Dict[str, Any] = {}
37 | 
38 |     def get_config(self) -> ServerConfig:
39 |         """Get the current configuration."""
40 |         # Always get the latest from the config manager
41 |         config = self.config_manager.get_config()
42 |         return config
43 | 
44 |     def register_dependency(self, name: str, instance: Any) -> None:
45 |         """Register an additional dependency."""
46 |         self._additional[name] = instance
47 | 
48 |     def get_dependency(self, name: str) -> Any:
49 |         """Get a registered dependency."""
50 |         return self._additional.get(name)
51 | 
52 | 
53 | # Create the single container instance - this will be the ONLY global
54 | container = DependencyContainer()
55 | 
56 | 
57 | def get_container() -> DependencyContainer:
58 |     """Get the dependency container."""
59 |     return container
60 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release
 2 | 
 3 | on:
 4 |   release:
 5 |     types: [published]
 6 | 
 7 | permissions:
 8 |   contents: read
 9 |   id-token: write
10 | 
11 | jobs:
12 |   release:
13 |     runs-on: ubuntu-latest
14 |     timeout-minutes: 5
15 |     steps:
16 |     - uses: actions/checkout@v4
17 |     
18 |     - name: Set up Python
19 |       uses: actions/setup-python@v5
20 |       with:
21 |         python-version: "3.12"
22 |     
23 |     - name: Install uv
24 |       run: |
25 |         curl -LsSf https://astral.sh/uv/install.sh | sh
26 |         echo "$HOME/.cargo/bin" >> $GITHUB_PATH
27 |     
28 |     - name: Install development dependencies
29 |       run: |
30 |         uv venv
31 |         source .venv/bin/activate
32 |         uv pip install -e ".[dev]"
33 |     
34 |     - name: Run comprehensive tests
35 |       run: |
36 |         source .venv/bin/activate
37 |         # Run linting and formatting
38 |         ruff check .
39 |         ruff format . --check
40 |         mypy src/mcp_server_tree_sitter
41 |         
42 |         # Run all tests (regular + diagnostics)
43 |         pytest tests
44 |         pytest tests/test_diagnostics/ -v
45 |       env:
46 |         PYTHONPATH: ${{ github.workspace }}/src
47 | 
48 |     - name: Ensure diagnostic results directory exists
49 |       if: always()
50 |       run: mkdir -p diagnostic_results
51 | 
52 |     - name: Create placeholder if needed
53 |       if: always()
54 |       run: |
55 |         if [ -z "$(ls -A diagnostic_results 2>/dev/null)" ]; then
56 |           echo '{"info": "No diagnostic results generated"}' > diagnostic_results/placeholder.json
57 |         fi
58 | 
59 |     - name: Archive diagnostic results
60 |       if: always()
61 |       uses: actions/upload-artifact@v4
62 |       with:
63 |         name: diagnostic-results-release
64 |         path: diagnostic_results/
65 |         retention-days: 7
66 |         if-no-files-found: warn
67 | 
68 |     - name: Install build dependencies
69 |       run: |
70 |         source .venv/bin/activate
71 |         uv pip install build twine
72 | 
73 |     - name: Build package
74 |       run: |
75 |         source .venv/bin/activate
76 |         python -m build
77 |     
78 |     - name: Test wheel
79 |       run: |
80 |         python -m pip install dist/*.whl
81 |         mcp-server-tree-sitter --help
82 | 
83 |     - name: Publish to PyPI
84 |       uses: pypa/gh-action-pypi-publish@release/v1
85 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/security.py:
--------------------------------------------------------------------------------

```python
 1 | """Security utilities for mcp-server-tree-sitter."""
 2 | 
 3 | import logging
 4 | from pathlib import Path
 5 | from typing import Union
 6 | 
 7 | from ..api import get_config
 8 | from ..exceptions import SecurityError
 9 | 
10 | 
11 | def validate_file_access(file_path: Union[str, Path], project_root: Union[str, Path]) -> None:
12 |     """
13 |     Validate a file can be safely accessed.
14 | 
15 |     Args:
16 |         file_path: Path to validate
17 |         project_root: Project root directory
18 | 
19 |     Raises:
20 |         SecurityError: If path fails validation
21 |     """
22 |     # Always get a fresh config for each validation
23 |     config = get_config()
24 |     logger = logging.getLogger(__name__)
25 | 
26 |     path_obj = Path(file_path)
27 |     root_obj = Path(project_root)
28 | 
29 |     # Normalize paths to prevent directory traversal
30 |     try:
31 |         normalized_path = path_obj.resolve()
32 |         normalized_root = root_obj.resolve()
33 |     except (ValueError, OSError) as e:
34 |         raise SecurityError(f"Invalid path: {e}") from e
35 | 
36 |     # Check if path is inside project root
37 |     if not str(normalized_path).startswith(str(normalized_root)):
38 |         raise SecurityError(f"Access denied: {file_path} is outside project root")
39 | 
40 |     # Check excluded directories
41 |     for excluded in config.security.excluded_dirs:
42 |         if excluded in normalized_path.parts:
43 |             raise SecurityError(f"Access denied to excluded directory: {excluded}")
44 | 
45 |     # Check file extension if restriction is enabled
46 |     if config.security.allowed_extensions and path_obj.suffix.lower()[1:] not in config.security.allowed_extensions:
47 |         raise SecurityError(f"File type not allowed: {path_obj.suffix}")
48 | 
49 |     # Check file size if it exists
50 |     if normalized_path.exists() and normalized_path.is_file():
51 |         file_size_mb = normalized_path.stat().st_size / (1024 * 1024)
52 |         max_file_size_mb = config.security.max_file_size_mb
53 |         logger.debug(f"File size check: {file_size_mb:.2f}MB, limit: {max_file_size_mb}MB")
54 |         if file_size_mb > max_file_size_mb:
55 |             raise SecurityError(f"File too large: {file_size_mb:.2f}MB exceeds limit of {max_file_size_mb}MB")
56 | 
```

--------------------------------------------------------------------------------
/tests/test_persistent_server.py:
--------------------------------------------------------------------------------

```python
 1 | """Tests for the persistent MCP server implementation."""
 2 | 
 3 | import tempfile
 4 | 
 5 | from mcp_server_tree_sitter.models.project import ProjectRegistry
 6 | from mcp_server_tree_sitter.server import (
 7 |     mcp,
 8 | )  # Was previously importing from persistent_server
 9 | 
10 | # Use the actual project registry for persistence tests
11 | project_registry = ProjectRegistry()
12 | 
13 | 
14 | def test_persistent_mcp_instance() -> None:
15 |     """Test that the persistent MCP instance works properly."""
16 |     # Simply check that the instance exists
17 |     assert mcp is not None
18 |     assert mcp.name == "tree_sitter"
19 | 
20 | 
21 | def test_persistent_project_registration() -> None:
22 |     """Test that project registration persists across different functions."""
23 |     # We can't directly clear projects in the new design
24 |     # Instead, let's just work with existing ones
25 | 
26 |     # Create a temporary directory
27 |     with tempfile.TemporaryDirectory() as temp_dir:
28 |         project_name = "persistent_test"
29 | 
30 |         # Register a project directly using the registry
31 |         project = project_registry.register_project(project_name, temp_dir)
32 | 
33 |         # Verify it was registered
34 |         assert project.name == project_name
35 |         all_projects = project_registry.list_projects()
36 |         project_names = [p["name"] for p in all_projects]
37 |         assert project_name in project_names
38 | 
39 |         # Get the project again to verify persistence
40 |         project2 = project_registry.get_project(project_name)
41 |         assert project2.name == project_name
42 | 
43 |         # List projects to verify it's included
44 |         projects = project_registry.list_projects()
45 |         assert any(p["name"] == project_name for p in projects)
46 | 
47 | 
48 | def test_project_registry_singleton() -> None:
49 |     """Test that project_registry is a singleton that persists."""
50 |     # Check singleton behavior
51 |     registry1 = ProjectRegistry()
52 |     registry2 = ProjectRegistry()
53 | 
54 |     # Should be the same instance
55 |     assert registry1 is registry2
56 | 
57 |     # Get projects from both registries
58 |     projects1 = registry1.list_projects()
59 |     projects2 = registry2.list_projects()
60 | 
61 |     # Should have the same number of projects
62 |     assert len(projects1) == len(projects2)
63 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [build-system]
 2 | requires = ["hatchling"]
 3 | build-backend = "hatchling.build"
 4 | 
 5 | [project]
 6 | name = "mcp-server-tree-sitter"
 7 | version = "0.5.1"
 8 | description = "MCP Server for Tree-sitter code analysis"
 9 | readme = "README.md"
10 | requires-python = ">=3.10"
11 | license = {text = "MIT"}
12 | authors = [
13 |     {name = "Wrale LTD", email = "[email protected]"}
14 | ]
15 | classifiers = [
16 |     "Development Status :: 3 - Alpha",
17 |     "Intended Audience :: Developers",
18 |     "License :: OSI Approved :: MIT License",
19 |     "Programming Language :: Python :: 3",
20 |     "Programming Language :: Python :: 3.10",
21 |     "Programming Language :: Python :: 3.11",
22 |     "Programming Language :: Python :: 3.12",
23 | ]
24 | dependencies = [
25 |     "mcp[cli]>=0.12.0",
26 |     "tree-sitter>=0.20.0",
27 |     "tree-sitter-language-pack>=0.6.1",
28 |     "pyyaml>=6.0",
29 |     "pydantic>=2.0.0",
30 |     "types-pyyaml>=6.0.12.20241230",
31 | ]
32 | 
33 | [project.optional-dependencies]
34 | dev = [
35 |     "pytest>=7.0.0",
36 |     "pytest-cov>=4.0.0",
37 |     "ruff>=0.0.262",
38 |     "mypy>=1.2.0",
39 | ]
40 | # Language support (now included via tree-sitter-language-pack)
41 | languages = [
42 |     # No individual languages needed as tree-sitter-language-pack provides all
43 | ]
44 | 
45 | [project.urls]
46 | "Homepage" = "https://github.com/wrale/mcp-server-tree-sitter"
47 | "Bug Tracker" = "https://github.com/wrale/mcp-server-tree-sitter/issues"
48 | 
49 | [project.scripts]
50 | mcp-server-tree-sitter = "mcp_server_tree_sitter.server:main"
51 | 
52 | [tool.hatch.build.targets.wheel]
53 | packages = ["src/mcp_server_tree_sitter"]
54 | 
55 | [tool.pytest.ini_options]
56 | testpaths = ["tests"]
57 | python_files = "test_*.py"
58 | python_classes = "Test*"
59 | python_functions = "test_*"
60 | markers = [
61 |     "diagnostic: mark test as producing diagnostic information",
62 | ]
63 | 
64 | [tool.mypy]
65 | python_version = "3.10"
66 | warn_return_any = true
67 | warn_unused_configs = true
68 | disallow_untyped_defs = true
69 | disallow_incomplete_defs = true
70 | 
71 | [[tool.mypy.overrides]]
72 | module = "tree_sitter.*"
73 | ignore_missing_imports = true
74 | 
75 | [[tool.mypy.overrides]]
76 | module = "tests.*"
77 | disallow_untyped_defs = false
78 | disallow_incomplete_defs = false
79 | check_untyped_defs = false
80 | warn_return_any = false
81 | warn_no_return = false
82 | 
83 | [tool.ruff]
84 | line-length = 120
85 | target-version = "py310"
86 | 
87 | [tool.ruff.lint]
88 | select = ["E", "F", "I", "W", "B"]
89 | 
```

--------------------------------------------------------------------------------
/docs/cli.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Tree-sitter Server CLI Guide
  2 | 
  3 | This document explains the command-line interface (CLI) for the MCP Tree-sitter Server, including available options and usage patterns.
  4 | 
  5 | ## Command-Line Arguments
  6 | 
  7 | The MCP Tree-sitter Server provides a command-line interface with several options:
  8 | 
  9 | ```bash
 10 | mcp-server-tree-sitter [options]
 11 | ```
 12 | 
 13 | ### Available Options
 14 | 
 15 | | Option | Description |
 16 | |--------|-------------|
 17 | | `--help` | Show help message and exit |
 18 | | `--version` | Show version information and exit |
 19 | | `--config CONFIG` | Path to configuration file |
 20 | | `--debug` | Enable debug logging |
 21 | | `--disable-cache` | Disable parse tree caching |
 22 | 
 23 | ### Examples
 24 | 
 25 | Display help information:
 26 | ```bash
 27 | mcp-server-tree-sitter --help
 28 | ```
 29 | 
 30 | Show version information:
 31 | ```bash
 32 | mcp-server-tree-sitter --version
 33 | ```
 34 | 
 35 | Run with a custom configuration file:
 36 | ```bash
 37 | mcp-server-tree-sitter --config /path/to/config.yaml
 38 | ```
 39 | 
 40 | Enable debug logging:
 41 | ```bash
 42 | mcp-server-tree-sitter --debug
 43 | ```
 44 | 
 45 | Disable parse tree caching:
 46 | ```bash
 47 | mcp-server-tree-sitter --disable-cache
 48 | ```
 49 | 
 50 | ## Running with MCP
 51 | 
 52 | The server can also be run using the MCP command-line interface:
 53 | 
 54 | ```bash
 55 | # Run the server
 56 | mcp run mcp_server_tree_sitter.server
 57 | 
 58 | # Run with the MCP Inspector
 59 | mcp dev mcp_server_tree_sitter.server
 60 | ```
 61 | 
 62 | You can pass the same arguments to these commands:
 63 | 
 64 | ```bash
 65 | # Enable debug logging
 66 | mcp run mcp_server_tree_sitter.server --debug
 67 | 
 68 | # Use a custom configuration file with the inspector
 69 | mcp dev mcp_server_tree_sitter.server --config /path/to/config.yaml
 70 | ```
 71 | 
 72 | ## Using Makefile Targets
 73 | 
 74 | For convenience, the project provides Makefile targets for common operations:
 75 | 
 76 | ```bash
 77 | # Show available targets
 78 | make
 79 | 
 80 | # Run the server with default settings
 81 | make mcp-run
 82 | 
 83 | # Run with specific arguments
 84 | make mcp-run ARGS="--debug --config /path/to/config.yaml"
 85 | 
 86 | # Run with the inspector
 87 | make mcp-dev ARGS="--debug"
 88 | ```
 89 | 
 90 | ## Environment Variables
 91 | 
 92 | The server also supports configuration through environment variables:
 93 | 
 94 | ```bash
 95 | # Set log level
 96 | export MCP_TS_LOG_LEVEL=DEBUG
 97 | 
 98 | # Set configuration file path
 99 | export MCP_TS_CONFIG_PATH=/path/to/config.yaml
100 | 
101 | # Run the server
102 | mcp-server-tree-sitter
103 | ```
104 | 
105 | See the [Configuration Guide](./config.md) for more details on environment variables and configuration options.
106 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/path.py:
--------------------------------------------------------------------------------

```python
 1 | """Path utilities for mcp-server-tree-sitter."""
 2 | 
 3 | import os
 4 | from pathlib import Path
 5 | from typing import Union
 6 | 
 7 | 
 8 | def normalize_path(path: Union[str, Path], ensure_absolute: bool = False) -> Path:
 9 |     """
10 |     Normalize a path for cross-platform compatibility.
11 | 
12 |     Args:
13 |         path: Path string or object
14 |         ensure_absolute: If True, raises ValueError for relative paths
15 | 
16 |     Returns:
17 |         Normalized Path object
18 |     """
19 |     path_obj = Path(path).expanduser().resolve()
20 | 
21 |     if ensure_absolute and not path_obj.is_absolute():
22 |         raise ValueError(f"Path must be absolute: {path}")
23 | 
24 |     return path_obj
25 | 
26 | 
27 | def safe_relative_path(path: Union[str, Path], base: Union[str, Path]) -> Path:
28 |     """
29 |     Safely get a relative path that prevents directory traversal attacks.
30 | 
31 |     Args:
32 |         path: Target path
33 |         base: Base directory that should contain the path
34 | 
35 |     Returns:
36 |         Relative path object
37 | 
38 |     Raises:
39 |         ValueError: If path attempts to escape base directory
40 |     """
41 |     base_path = normalize_path(base)
42 |     target_path = normalize_path(path)
43 | 
44 |     # Ensure target is within base
45 |     try:
46 |         relative = target_path.relative_to(base_path)
47 |         # Check for directory traversal
48 |         if ".." in str(relative).split(os.sep):
49 |             raise ValueError(f"Path contains forbidden directory traversal: {path}")
50 |         return relative
51 |     except ValueError as e:
52 |         raise ValueError(f"Path {path} is not within base directory {base}") from e
53 | 
54 | 
55 | def get_project_root(path: Union[str, Path]) -> Path:
56 |     """
57 |     Attempt to determine project root from a file path by looking for common markers.
58 | 
59 |     Args:
60 |         path: Path to start from (file or directory)
61 | 
62 |     Returns:
63 |         Path to likely project root
64 |     """
65 |     path_obj = normalize_path(path)
66 | 
67 |     # If path is a file, start from its directory
68 |     if path_obj.is_file():
69 |         path_obj = path_obj.parent
70 | 
71 |     # Look for common project indicators
72 |     markers = [
73 |         ".git",
74 |         "pyproject.toml",
75 |         "setup.py",
76 |         "package.json",
77 |         "Cargo.toml",
78 |         "CMakeLists.txt",
79 |         ".svn",
80 |         "Makefile",
81 |     ]
82 | 
83 |     # Start from path and go up directories until a marker is found
84 |     current = path_obj
85 |     while current != current.parent:  # Stop at filesystem root
86 |         for marker in markers:
87 |             if (current / marker).exists():
88 |                 return current
89 |         current = current.parent
90 | 
91 |     # If no marker found, return original directory
92 |     return path_obj
93 | 
```

--------------------------------------------------------------------------------
/tests/test_basic.py:
--------------------------------------------------------------------------------

```python
 1 | """Basic tests for mcp-server-tree-sitter."""
 2 | 
 3 | import tempfile
 4 | 
 5 | from mcp_server_tree_sitter.config import ServerConfig
 6 | from mcp_server_tree_sitter.language.registry import LanguageRegistry
 7 | from mcp_server_tree_sitter.models.project import ProjectRegistry
 8 | 
 9 | 
10 | def test_config_default() -> None:
11 |     """Test that default configuration is loaded."""
12 |     # Create a default configuration
13 |     config = ServerConfig()
14 | 
15 |     # Check defaults
16 |     assert config.cache.enabled is True
17 |     assert config.cache.max_size_mb == 100
18 |     assert config.security.max_file_size_mb == 5
19 |     assert ".git" in config.security.excluded_dirs
20 | 
21 | 
22 | def test_project_registry() -> None:
23 |     """Test project registry functionality."""
24 |     registry = ProjectRegistry()
25 | 
26 |     # Create a temporary directory
27 |     with tempfile.TemporaryDirectory() as temp_dir:
28 |         # Register a project
29 |         project = registry.register_project("test", temp_dir)
30 | 
31 |         # Check project details
32 |         assert project.name == "test"
33 |         # Use os.path.samefile to compare paths instead of string comparison
34 |         # This handles platform-specific path normalization
35 |         # (e.g., /tmp -> /private/tmp on macOS)
36 |         import os
37 | 
38 |         assert os.path.samefile(str(project.root_path), temp_dir)
39 | 
40 |         # List projects
41 |         projects = registry.list_projects()
42 |         assert len(projects) == 1
43 |         assert projects[0]["name"] == "test"
44 | 
45 |         # Get project
46 |         project2 = registry.get_project("test")
47 |         assert project2.name == "test"
48 | 
49 |         # Remove project
50 |         registry.remove_project("test")
51 |         projects = registry.list_projects()
52 |         assert len(projects) == 0
53 | 
54 | 
55 | def test_language_registry() -> None:
56 |     """Test language registry functionality."""
57 |     registry = LanguageRegistry()
58 | 
59 |     # Test language detection
60 |     assert registry.language_for_file("test.py") == "python"
61 |     assert registry.language_for_file("script.js") == "javascript"
62 |     assert registry.language_for_file("style.css") == "css"
63 | 
64 |     # Test available languages
65 |     languages = registry.list_available_languages()
66 |     assert isinstance(languages, list)
67 | 
68 |     # Test installable languages (should be empty now with language-pack)
69 |     installable = registry.list_installable_languages()
70 |     assert isinstance(installable, list)
71 |     assert len(installable) == 0  # No languages need to be separately installed
72 | 
73 | 
74 | if __name__ == "__main__":
75 |     # Run tests
76 |     test_config_default()
77 |     test_project_registry()
78 |     test_language_registry()
79 |     print("All tests passed!")
80 | 
```

--------------------------------------------------------------------------------
/tests/test_language_listing.py:
--------------------------------------------------------------------------------

```python
 1 | """Test for language listing functionality."""
 2 | 
 3 | from mcp_server_tree_sitter.language.registry import LanguageRegistry
 4 | from tests.test_helpers import check_language_available, list_languages
 5 | 
 6 | 
 7 | def test_list_available_languages() -> None:
 8 |     """Test that list_available_languages returns languages correctly."""
 9 |     registry = LanguageRegistry()
10 | 
11 |     # Get available languages
12 |     available_languages = registry.list_available_languages()
13 | 
14 |     # Check for common languages we expect to be available
15 |     expected_languages = [
16 |         "python",
17 |         "javascript",
18 |         "typescript",
19 |         "c",
20 |         "cpp",
21 |         "go",
22 |         "rust",
23 |     ]
24 | 
25 |     # Assert that we have languages available
26 |     assert len(available_languages) > 0, "No languages available"
27 | 
28 |     # Assert that we find at least some of our expected languages
29 |     for lang in expected_languages:
30 |         assert lang in available_languages, f"Expected language {lang} not in available languages"
31 | 
32 | 
33 | def test_language_api_consistency() -> None:
34 |     """Test consistency between language detection and language listing."""
35 |     registry = LanguageRegistry()
36 | 
37 |     # Test with a few common languages
38 |     test_languages = [
39 |         "python",
40 |         "javascript",
41 |         "typescript",
42 |         "c",
43 |         "cpp",
44 |         "go",
45 |         "rust",
46 |     ]
47 | 
48 |     # Check each language both through is_language_available and list_available_languages
49 |     available_languages = registry.list_available_languages()
50 | 
51 |     for lang in test_languages:
52 |         is_available = registry.is_language_available(lang)
53 |         is_listed = lang in available_languages
54 | 
55 |         # Both methods should return the same result
56 |         assert is_available == is_listed, f"Inconsistency for {lang}: available={is_available}, listed={is_listed}"
57 | 
58 | 
59 | def test_server_language_tools() -> None:
60 |     """Test the server language tools."""
61 |     # Test list_languages
62 |     languages_result = list_languages()
63 |     assert "available" in languages_result, "Missing 'available' key in list_languages result"
64 |     assert isinstance(languages_result["available"], list), "'available' should be a list"
65 |     assert len(languages_result["available"]) > 0, "No languages available"
66 | 
67 |     # Test each language with check_language_available
68 |     for lang in ["python", "javascript", "typescript"]:
69 |         result = check_language_available(lang)
70 |         assert result["status"] == "success", f"Language {lang} should be available"
71 |         assert "message" in result, "Missing 'message' key in check_language_available result"
72 | 
73 | 
74 | if __name__ == "__main__":
75 |     test_list_available_languages()
76 |     test_language_api_consistency()
77 |     test_server_language_tools()
78 |     print("All tests passed!")
79 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/__main__.py:
--------------------------------------------------------------------------------

```python
 1 | """Main entry point for mcp-server-tree-sitter."""
 2 | 
 3 | import argparse
 4 | import os
 5 | import sys
 6 | 
 7 | from .bootstrap import get_logger, update_log_levels
 8 | from .config import load_config
 9 | from .context import global_context
10 | from .server import mcp
11 | 
12 | # Get a properly configured logger
13 | logger = get_logger(__name__)
14 | 
15 | 
16 | def main() -> int:
17 |     """Run the server with optional arguments."""
18 |     # Parse command line arguments
19 |     parser = argparse.ArgumentParser(description="MCP Tree-sitter Server - Code analysis with tree-sitter")
20 |     parser.add_argument("--config", help="Path to configuration file")
21 |     parser.add_argument("--debug", action="store_true", help="Enable debug logging")
22 |     parser.add_argument("--disable-cache", action="store_true", help="Disable parse tree caching")
23 |     parser.add_argument("--version", action="store_true", help="Show version and exit")
24 | 
25 |     args = parser.parse_args()
26 | 
27 |     # Handle version display
28 |     if args.version:
29 |         import importlib.metadata
30 | 
31 |         try:
32 |             version = importlib.metadata.version("mcp-server-tree-sitter")
33 |             print(f"mcp-server-tree-sitter version {version}")
34 |         except importlib.metadata.PackageNotFoundError:
35 |             print("mcp-server-tree-sitter (version unknown - package not installed)")
36 |         return 0
37 | 
38 |     # Set up logging level
39 |     if args.debug:
40 |         # Set environment variable first for consistency
41 |         os.environ["MCP_TS_LOG_LEVEL"] = "DEBUG"
42 |         # Then update log levels
43 |         update_log_levels("DEBUG")
44 |         logger.debug("Debug logging enabled")
45 | 
46 |     # Load configuration
47 |     try:
48 |         config = load_config(args.config)
49 | 
50 |         # Update global context with config
51 |         if args.config:
52 |             global_context.config_manager.load_from_file(args.config)
53 |         else:
54 |             # Update individual settings from config
55 |             global_context.config_manager.update_value("cache.enabled", config.cache.enabled)
56 |             global_context.config_manager.update_value("cache.max_size_mb", config.cache.max_size_mb)
57 |             global_context.config_manager.update_value("security.max_file_size_mb", config.security.max_file_size_mb)
58 |             global_context.config_manager.update_value("language.default_max_depth", config.language.default_max_depth)
59 | 
60 |         logger.debug("Configuration loaded successfully")
61 |     except Exception as e:
62 |         logger.error(f"Error loading configuration: {e}")
63 |         return 1
64 | 
65 |     # Run the server
66 |     try:
67 |         logger.info("Starting MCP Tree-sitter Server (with state persistence)")
68 |         mcp.run()
69 |     except KeyboardInterrupt:
70 |         logger.info("Server stopped by user")
71 |     except Exception as e:
72 |         logger.error(f"Error running server: {e}")
73 |         return 1
74 | 
75 |     return 0
76 | 
77 | 
78 | if __name__ == "__main__":
79 |     sys.exit(main())
80 | 
```

--------------------------------------------------------------------------------
/tests/test_ast_cursor.py:
--------------------------------------------------------------------------------

```python
 1 | """Test the cursor-based AST implementation."""
 2 | 
 3 | import tempfile
 4 | from pathlib import Path
 5 | 
 6 | from mcp_server_tree_sitter.language.registry import LanguageRegistry
 7 | from mcp_server_tree_sitter.models.ast_cursor import node_to_dict_cursor
 8 | from mcp_server_tree_sitter.utils.file_io import read_binary_file
 9 | from mcp_server_tree_sitter.utils.tree_sitter_helpers import create_parser, parse_source
10 | 
11 | 
12 | def test_cursor_based_ast() -> None:
13 |     """Test that the cursor-based AST node_to_dict function works."""
14 |     # Create a temporary test file
15 |     with tempfile.NamedTemporaryFile(suffix=".py", mode="w+") as f:
16 |         f.write("def hello():\n    print('Hello, world!')\n\nhello()\n")
17 |         f.flush()
18 | 
19 |         file_path = Path(f.name)
20 | 
21 |         # Set up language registry
22 |         registry = LanguageRegistry()
23 |         language = registry.language_for_file(file_path.name)
24 |         assert language is not None, "Could not detect language for test file"
25 |         language_obj = registry.get_language(language)
26 | 
27 |         # Parse the file
28 |         parser = create_parser(language_obj)
29 |         source_bytes = read_binary_file(file_path)
30 |         tree = parse_source(source_bytes, parser)
31 | 
32 |         # Get AST using cursor-based approach
33 |         cursor_ast = node_to_dict_cursor(tree.root_node, source_bytes, max_depth=3)
34 | 
35 |         # Basic validation
36 |         assert "id" in cursor_ast, "AST should include node ID"
37 |         assert cursor_ast["type"] == "module", "Root node should be a module"
38 |         assert "children" in cursor_ast, "AST should include children"
39 |         assert len(cursor_ast["children"]) > 0, "AST should have at least one child"
40 | 
41 |         # Check function definition
42 |         if cursor_ast["children"]:
43 |             function_node = cursor_ast["children"][0]
44 |             assert function_node["type"] == "function_definition", "Expected function definition"
45 | 
46 |             # Check if children are properly included
47 |             assert "children" in function_node, "Function should have children"
48 |             assert function_node["children_count"] > 0, "Function should have children"
49 | 
50 |             # Verify some function components exist
51 |             function_children_types = [child["type"] for child in function_node["children"]]
52 |             assert "identifier" in function_children_types, "Function should have identifier"
53 | 
54 |             # Verify text extraction works if available
55 |             if "text" in function_node:
56 |                 # Check for 'hello' in the text, handling both string and bytes
57 |                 if isinstance(function_node["text"], bytes):
58 |                     assert b"hello" in function_node["text"], "Function text should contain 'hello'"
59 |                 else:
60 |                     assert "hello" in function_node["text"], "Function text should contain 'hello'"
61 | 
62 | 
63 | if __name__ == "__main__":
64 |     test_cursor_based_ast()
65 |     print("All tests passed!")
66 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: CI
  2 | 
  3 | on:
  4 |   push:
  5 |     branches: [ main ]
  6 |   pull_request:
  7 |     branches: [ main ]
  8 | 
  9 | jobs:
 10 |   test:
 11 |     runs-on: ubuntu-latest
 12 |     strategy:
 13 |       matrix:
 14 |         python-version: ["3.12"]
 15 |         install-method: ["uv", "uvx"]
 16 | 
 17 |     steps:
 18 |     - uses: actions/checkout@v4
 19 |     
 20 |     - name: Set up Python ${{ matrix.python-version }}
 21 |       uses: actions/setup-python@v5
 22 |       with:
 23 |         python-version: ${{ matrix.python-version }}
 24 |     
 25 |     - name: Install uv
 26 |       run: |
 27 |         curl -LsSf https://astral.sh/uv/install.sh | sh
 28 |         echo "$HOME/.cargo/bin" >> $GITHUB_PATH
 29 | 
 30 |     - name: Install dependencies with uv
 31 |       if: matrix.install-method == 'uv'
 32 |       run: |
 33 |         uv venv
 34 |         source .venv/bin/activate
 35 |         uv pip install -e ".[dev]"
 36 |         which ruff
 37 |         which python
 38 | 
 39 |     - name: Install globally with uvx (system-wide)
 40 |       if: matrix.install-method == 'uvx'
 41 |       run: |
 42 |         python -m pip install -e ".[dev]"
 43 |         which ruff
 44 |         which python
 45 | 
 46 |     - name: Run checks and tests (uv)
 47 |       if: matrix.install-method == 'uv'
 48 |       run: |
 49 |         source .venv/bin/activate
 50 |         # Linting and formatting
 51 |         ruff check .
 52 |         ruff format . --check
 53 |         mypy src/mcp_server_tree_sitter
 54 |         # Run all tests including diagnostics
 55 |         pytest tests
 56 |         pytest tests/test_diagnostics/ -v
 57 |       env:
 58 |         PYTHONPATH: ${{ github.workspace }}/src
 59 | 
 60 |     - name: Run checks and tests (system)
 61 |       if: matrix.install-method == 'uvx'
 62 |       run: |
 63 |         # Linting and formatting
 64 |         ruff check .
 65 |         ruff format . --check
 66 |         mypy src/mcp_server_tree_sitter
 67 |         # Run all tests including diagnostics
 68 |         pytest tests
 69 |         pytest tests/test_diagnostics/ -v
 70 |       env:
 71 |         PYTHONPATH: ${{ github.workspace }}/src
 72 |     
 73 |     - name: Ensure diagnostic results directory exists
 74 |       if: always()
 75 |       run: mkdir -p diagnostic_results
 76 | 
 77 |     - name: Create placeholder if needed
 78 |       if: always()
 79 |       run: |
 80 |         if [ -z "$(ls -A diagnostic_results 2>/dev/null)" ]; then
 81 |           echo '{"info": "No diagnostic results generated"}' > diagnostic_results/placeholder.json
 82 |         fi
 83 | 
 84 |     - name: Archive diagnostic results
 85 |       if: always()
 86 |       uses: actions/upload-artifact@v4
 87 |       with:
 88 |         name: diagnostic-results-${{ matrix.install-method }}
 89 |         path: diagnostic_results/
 90 |         retention-days: 7
 91 |         if-no-files-found: warn
 92 | 
 93 |   verify-uvx:
 94 |     runs-on: ubuntu-latest
 95 |     timeout-minutes: 5
 96 |     steps:
 97 |     - uses: actions/checkout@v4
 98 |     
 99 |     - name: Set up Python 3.12
100 |       uses: actions/setup-python@v5
101 |       with:
102 |         python-version: "3.12"
103 |     
104 |     - name: Install build dependencies
105 |       run: |
106 |         python -m pip install build
107 |         python -m pip install uv
108 | 
109 |     - name: Build package
110 |       run: python -m build
111 | 
112 |     - name: Install and verify
113 |       run: |
114 |         python -m pip install dist/*.whl
115 |         mcp-server-tree-sitter --help
116 | 
```

--------------------------------------------------------------------------------
/tests/test_cli_arguments.py:
--------------------------------------------------------------------------------

```python
 1 | """Tests for command-line argument handling."""
 2 | 
 3 | import subprocess
 4 | import sys
 5 | from unittest.mock import patch
 6 | 
 7 | import pytest
 8 | 
 9 | from mcp_server_tree_sitter.server import main
10 | 
11 | 
12 | def test_help_flag_does_not_start_server():
13 |     """Test that --help flag prints help and doesn't start the server."""
14 |     # Use subprocess to test the actual command
15 |     result = subprocess.run(
16 |         [sys.executable, "-m", "mcp_server_tree_sitter", "--help"],
17 |         capture_output=True,
18 |         text=True,
19 |         check=False,
20 |     )
21 | 
22 |     # Check that it exited successfully
23 |     assert result.returncode == 0
24 | 
25 |     # Check that the help text was printed
26 |     assert "MCP Tree-sitter Server" in result.stdout
27 |     assert "--help" in result.stdout
28 |     assert "--config" in result.stdout
29 | 
30 |     # Server should not have started - no startup messages
31 |     assert "Starting MCP Tree-sitter Server" not in result.stdout
32 | 
33 | 
34 | def test_version_flag_exits_without_starting_server():
35 |     """Test that --version shows version and exits without starting the server."""
36 |     result = subprocess.run(
37 |         [sys.executable, "-m", "mcp_server_tree_sitter", "--version"],
38 |         capture_output=True,
39 |         text=True,
40 |         check=False,
41 |     )
42 | 
43 |     # Check that it exited successfully
44 |     assert result.returncode == 0
45 | 
46 |     # Check that the version was printed
47 |     assert "mcp-server-tree-sitter version" in result.stdout
48 | 
49 |     # Server should not have started
50 |     assert "Starting MCP Tree-sitter Server" not in result.stdout
51 | 
52 | 
53 | def test_direct_script_help_flag():
54 |     """Test that mcp-server-tree-sitter --help works correctly when called as a script."""
55 |     # This uses a mock to avoid actually calling the script binary
56 |     with (
57 |         patch("sys.argv", ["mcp-server-tree-sitter", "--help"]),
58 |         patch("argparse.ArgumentParser.parse_args") as mock_parse_args,
59 |         # We don't actually need to use mock_exit in the test,
60 |         # but we still want to patch sys.exit to prevent actual exits
61 |         patch("sys.exit"),
62 |     ):
63 |         # Mock the ArgumentParser.parse_args to simulate --help behavior
64 |         # When --help is used, argparse exits with code 0 after printing help
65 |         mock_parse_args.side_effect = SystemExit(0)
66 | 
67 |         # This should catch the SystemExit raised by parse_args
68 |         with pytest.raises(SystemExit) as excinfo:
69 |             main()
70 | 
71 |         # Verify it's exiting with code 0 (success)
72 |         assert excinfo.value.code == 0
73 | 
74 | 
75 | def test_entry_point_implementation():
76 |     """Verify that the entry point properly uses argparse for argument handling."""
77 |     import inspect
78 | 
79 |     from mcp_server_tree_sitter.server import main
80 | 
81 |     # Get the source code of the main function
82 |     source = inspect.getsource(main)
83 | 
84 |     # Check that it's using argparse
85 |     assert "argparse.ArgumentParser" in source
86 |     assert "parse_args" in source
87 | 
88 |     # Check for proper handling of key flags
89 |     assert "--help" in source or "automatically" in source  # argparse adds --help automatically
90 |     assert "--version" in source
91 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/tools/debug.py:
--------------------------------------------------------------------------------

```python
  1 | """Debug tools for diagnosing configuration issues."""
  2 | 
  3 | from pathlib import Path
  4 | from typing import Any, Dict
  5 | 
  6 | import yaml
  7 | 
  8 | from ..config import ServerConfig, update_config_from_new
  9 | from ..context import global_context
 10 | 
 11 | 
 12 | def diagnose_yaml_config(config_path: str) -> Dict[str, Any]:
 13 |     """Diagnose issues with YAML configuration loading.
 14 | 
 15 |     Args:
 16 |         config_path: Path to YAML config file
 17 | 
 18 |     Returns:
 19 |         Dictionary with diagnostic information
 20 |     """
 21 |     result = {
 22 |         "file_path": config_path,
 23 |         "exists": False,
 24 |         "readable": False,
 25 |         "yaml_valid": False,
 26 |         "parsed_data": None,
 27 |         "config_before": None,
 28 |         "config_after": None,
 29 |         "error": None,
 30 |     }
 31 | 
 32 |     # Check if file exists
 33 |     path_obj = Path(config_path)
 34 |     result["exists"] = path_obj.exists()
 35 | 
 36 |     if not result["exists"]:
 37 |         result["error"] = f"File does not exist: {config_path}"
 38 |         return result
 39 | 
 40 |     # Check if file is readable
 41 |     try:
 42 |         with open(path_obj, "r") as f:
 43 |             content = f.read()
 44 |             result["readable"] = True
 45 |             result["file_content"] = content
 46 |     except Exception as e:
 47 |         result["error"] = f"Error reading file: {str(e)}"
 48 |         return result
 49 | 
 50 |     # Try to parse YAML
 51 |     try:
 52 |         config_data = yaml.safe_load(content)
 53 |         result["yaml_valid"] = True
 54 |         result["parsed_data"] = config_data
 55 |     except Exception as e:
 56 |         result["error"] = f"Error parsing YAML: {str(e)}"
 57 |         return result
 58 | 
 59 |     # Check if parsed data is None or empty
 60 |     if config_data is None:
 61 |         result["error"] = "YAML parser returned None (file empty or contains only comments)"
 62 |         return result
 63 | 
 64 |     if not isinstance(config_data, dict):
 65 |         result["error"] = f"YAML parser returned non-dict: {type(config_data)}"
 66 |         return result
 67 | 
 68 |     # Try creating a new config
 69 |     try:
 70 |         # Get current config
 71 |         current_config = global_context.get_config()
 72 |         result["config_before"] = {
 73 |             "cache.max_size_mb": current_config.cache.max_size_mb,
 74 |             "security.max_file_size_mb": current_config.security.max_file_size_mb,
 75 |             "language.default_max_depth": current_config.language.default_max_depth,
 76 |         }
 77 | 
 78 |         # Create new config from parsed data
 79 |         new_config = ServerConfig(**config_data)
 80 | 
 81 |         # Before update
 82 |         result["new_config"] = {
 83 |             "cache.max_size_mb": new_config.cache.max_size_mb,
 84 |             "security.max_file_size_mb": new_config.security.max_file_size_mb,
 85 |             "language.default_max_depth": new_config.language.default_max_depth,
 86 |         }
 87 | 
 88 |         # Update config
 89 |         update_config_from_new(current_config, new_config)
 90 | 
 91 |         # After update
 92 |         result["config_after"] = {
 93 |             "cache.max_size_mb": current_config.cache.max_size_mb,
 94 |             "security.max_file_size_mb": current_config.security.max_file_size_mb,
 95 |             "language.default_max_depth": current_config.language.default_max_depth,
 96 |         }
 97 | 
 98 |     except Exception as e:
 99 |         result["error"] = f"Error updating config: {str(e)}"
100 |         return result
101 | 
102 |     return result
103 | 
```

--------------------------------------------------------------------------------
/tests/test_makefile_targets.py:
--------------------------------------------------------------------------------

```python
 1 | """Tests for Makefile targets to ensure they execute correctly."""
 2 | 
 3 | import os
 4 | import re
 5 | import subprocess
 6 | from pathlib import Path
 7 | 
 8 | 
 9 | def test_makefile_target_syntax():
10 |     """Test that critical Makefile targets are correctly formed."""
11 |     # Get the Makefile content
12 |     makefile_path = Path(__file__).parent.parent / "Makefile"
13 |     with open(makefile_path, "r") as f:
14 |         makefile_content = f.read()
15 | 
16 |     # Test mcp targets - they should use uv run mcp directly
17 |     mcp_target_pattern = r"mcp-(run|dev|install):\n\t\$\(UV\) run mcp"
18 |     mcp_targets = re.findall(mcp_target_pattern, makefile_content)
19 | 
20 |     # We should find at least 3 matches (run, dev, install)
21 |     assert len(mcp_targets) >= 3, "Missing proper mcp invocation in Makefile targets"
22 | 
23 |     # Check for correct server module reference
24 |     assert "$(PACKAGE).server" in makefile_content, "Server module reference is incorrect"
25 | 
26 |     # Custom test for mcp-run
27 |     mcp_run_pattern = r"mcp-run:.*\n\t\$\(UV\) run mcp run \$\(PACKAGE\)\.server"
28 |     assert re.search(mcp_run_pattern, makefile_content), "mcp-run target is incorrectly formed"
29 | 
30 |     # Test that help is the default target
31 |     assert ".PHONY: all help" in makefile_content, "help is not properly declared as .PHONY"
32 |     assert "help: show-help" in makefile_content, "help is not properly set as default target"
33 | 
34 | 
35 | def test_makefile_target_execution():
36 |     """Test that Makefile targets execute correctly when invoked with --help."""
37 |     # We'll only try the --help flag since we don't want to actually start the server
38 |     # Skip if not in a development environment
39 |     if not os.path.exists("Makefile"):
40 |         print("Skipping test_makefile_target_execution: Makefile not found")
41 |         return
42 | 
43 |     # Skip this test in CI environment
44 |     if os.environ.get("CI") == "true" or os.environ.get("GITHUB_ACTIONS") == "true":
45 |         print("Skipping test_makefile_target_execution in CI environment")
46 |         return
47 | 
48 |     # Test mcp-run with --help
49 |     try:
50 |         # Use the make target with --help appended to see if it resolves correctly
51 |         # We capture stderr because sometimes help messages go there
52 |         result = subprocess.run(
53 |             ["make", "mcp-run", "ARGS=--help"],
54 |             capture_output=True,
55 |             text=True,
56 |             timeout=5,  # Don't let this run too long
57 |             check=False,
58 |             env={**os.environ, "MAKEFLAGS": ""},  # Clear any inherited make flags
59 |         )
60 | 
61 |         # The run shouldn't fail catastrophically
62 |         assert "File not found" not in result.stderr, "mcp-run can't find the module"
63 | 
64 |         # We expect to see help text in the output (stdout or stderr)
65 |         output = result.stdout + result.stderr
66 |         has_usage = "usage:" in output.lower() or "mcp run" in output
67 | 
68 |         # We don't fail the test if the help check fails - this is more of a warning
69 |         # since the environment might not be set up to run make directly
70 |         if not has_usage:
71 |             print("WARNING: Couldn't verify mcp-run --help output; environment may not be properly configured")
72 | 
73 |     except (subprocess.SubprocessError, FileNotFoundError) as e:
74 |         # Don't fail the test if we can't run make
75 |         print(f"WARNING: Couldn't execute make command; skipping execution check: {e}")
76 | 
```

--------------------------------------------------------------------------------
/tests/test_env_config.py:
--------------------------------------------------------------------------------

```python
 1 | """Tests for environment variable configuration overrides."""
 2 | 
 3 | import os
 4 | import tempfile
 5 | 
 6 | import pytest
 7 | import yaml
 8 | 
 9 | from mcp_server_tree_sitter.config import ConfigurationManager
10 | 
11 | 
12 | @pytest.fixture
13 | def temp_yaml_file():
14 |     """Create a temporary YAML file with test configuration."""
15 |     with tempfile.NamedTemporaryFile(suffix=".yaml", mode="w+", delete=False) as temp_file:
16 |         test_config = {
17 |             "cache": {"enabled": True, "max_size_mb": 256, "ttl_seconds": 3600},
18 |             "security": {"max_file_size_mb": 10, "excluded_dirs": [".git", "node_modules", "__pycache__", ".cache"]},
19 |             "language": {"auto_install": True, "default_max_depth": 7},
20 |         }
21 |         yaml.dump(test_config, temp_file)
22 |         temp_file.flush()
23 |         temp_file_path = temp_file.name
24 | 
25 |     yield temp_file_path
26 | 
27 |     # Clean up
28 |     os.unlink(temp_file_path)
29 | 
30 | 
31 | def test_env_overrides_defaults(monkeypatch):
32 |     """Environment variables should override hard-coded defaults."""
33 |     # Using single underscore format that matches current implementation
34 |     monkeypatch.setenv("MCP_TS_CACHE_MAX_SIZE_MB", "512")
35 | 
36 |     mgr = ConfigurationManager()
37 |     cfg = mgr.get_config()
38 | 
39 |     assert cfg.cache.max_size_mb == 512, "Environment variable should override default value"
40 |     # ensure other defaults stay intact
41 |     assert cfg.security.max_file_size_mb == 5
42 |     assert cfg.language.default_max_depth == 5
43 | 
44 | 
45 | def test_env_overrides_yaml(temp_yaml_file, monkeypatch):
46 |     """Environment variables should take precedence over YAML values."""
47 |     # YAML sets 256; env var must win with 1024
48 |     # Using single underscore format that matches current implementation
49 |     monkeypatch.setenv("MCP_TS_CACHE_MAX_SIZE_MB", "1024")
50 | 
51 |     # Also set a security env var to verify multiple variables work
52 |     monkeypatch.setenv("MCP_TS_SECURITY_MAX_FILE_SIZE_MB", "15")
53 | 
54 |     mgr = ConfigurationManager()
55 |     # First load the YAML file
56 |     mgr.load_from_file(temp_yaml_file)
57 | 
58 |     # Get the loaded config
59 |     cfg = mgr.get_config()
60 | 
61 |     # Verify environment variables override YAML settings
62 |     assert cfg.cache.max_size_mb == 1024, "Environment variable should override YAML values"
63 |     assert cfg.security.max_file_size_mb == 15, "Environment variable should override YAML values"
64 | 
65 |     # But YAML values that aren't overridden by env vars should remain
66 |     assert cfg.cache.ttl_seconds == 3600
67 |     assert cfg.language.default_max_depth == 7
68 |     assert cfg.language.auto_install is True
69 | 
70 | 
71 | def test_log_level_env_var(monkeypatch):
72 |     """Test the specific MCP_TS_LOG_LEVEL variable that was the original issue."""
73 |     monkeypatch.setenv("MCP_TS_LOG_LEVEL", "DEBUG")
74 | 
75 |     mgr = ConfigurationManager()
76 |     cfg = mgr.get_config()
77 | 
78 |     assert cfg.log_level == "DEBUG", "Log level should be set from environment variable"
79 | 
80 | 
81 | def test_invalid_env_var_handling(monkeypatch):
82 |     """Test that invalid environment variable values don't crash the system."""
83 |     # Set an invalid value for an integer field
84 |     monkeypatch.setenv("MCP_TS_CACHE_MAX_SIZE_MB", "not_a_number")
85 | 
86 |     # This should not raise an exception
87 |     mgr = ConfigurationManager()
88 |     cfg = mgr.get_config()
89 | 
90 |     # The default value should be used
91 |     assert cfg.cache.max_size_mb == 100, "Invalid values should fall back to defaults"
92 | 
```

--------------------------------------------------------------------------------
/docs/tree-sitter-type-safety.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Tree-sitter Type Safety Guide
 2 | 
 3 | This document explains our approach to type safety when interfacing with the tree-sitter library and why certain type-checking suppressions are necessary.
 4 | 
 5 | ## Background
 6 | 
 7 | The MCP Tree-sitter Server maintains type safety through Python's type hints and mypy verification. However, when interfacing with external libraries like tree-sitter, we encounter challenges:
 8 | 
 9 | 1. Tree-sitter's Python bindings have inconsistent API signatures across versions
10 | 2. Tree-sitter objects don't always match our protocol definitions
11 | 3. The library may work at runtime but fail static type checking
12 | 
13 | ## Type Suppression Strategy
14 | 
15 | We use targeted `# type: ignore` comments to handle specific scenarios where mypy can't verify correctness, but our runtime code handles the variations properly.
16 | 
17 | ### Examples of Necessary Type Suppressions
18 | 
19 | #### Parser Interface Variations
20 | 
21 | Some versions of tree-sitter use `set_language()` while others use `language` as the attribute/method:
22 | 
23 | ```python
24 | try:
25 |     parser.set_language(safe_language)  # type: ignore
26 | except AttributeError:
27 |     if hasattr(parser, 'language'):
28 |         # Use the language method if available
29 |         parser.language = safe_language  # type: ignore
30 |     else:
31 |         # Fallback to setting the attribute directly
32 |         parser.language = safe_language  # type: ignore
33 | ```
34 | 
35 | #### Node Handling Safety
36 | 
37 | For cursor navigation and tree traversal, we need to handle potential `None` values:
38 | 
39 | ```python
40 | def visit(node: Optional[Node], field_name: Optional[str], depth: int) -> bool:
41 |     if node is None:
42 |         return False
43 |     # Continue with node operations...
44 | ```
45 | 
46 | ## Guidelines for Using Type Suppressions
47 | 
48 | 1. **Be specific**: Always use `# type: ignore` on the exact line with the issue, not for entire blocks or files
49 | 2. **Add comments**: Explain why the suppression is necessary
50 | 3. **Try alternatives first**: Only use suppressions after trying to fix the actual type issue
51 | 4. **Include runtime checks**: Always pair suppressions with runtime checks (try/except, if hasattr, etc.)
52 | 
53 | ## Our Pattern for Library Compatibility
54 | 
55 | We follow a consistent pattern for tree-sitter API compatibility:
56 | 
57 | 1. **Define Protocols**: Use Protocol classes to define expected interfaces
58 | 2. **Safe Type Casting**: Use wrapper functions like `ensure_node()` to safely cast objects
59 | 3. **Feature Detection**: Use `hasattr()` checks before accessing attributes
60 | 4. **Fallback Mechanisms**: Provide multiple ways to accomplish the same task
61 | 5. **Graceful Degradation**: Handle missing features by providing simplified alternatives
62 | 
63 | ## Testing Approach
64 | 
65 | Even with type suppressions, we ensure correctness through:
66 | 
67 | 1. Comprehensive test coverage for different tree-sitter operations
68 | 2. Tests with and without tree-sitter installed to verify fallback mechanisms
69 | 3. Runtime verification of object capabilities before operations
70 | 
71 | ## When to Update Type Suppressions
72 | 
73 | Review and potentially remove type suppressions when:
74 | 
75 | 1. Upgrading minimum supported tree-sitter version
76 | 2. Refactoring the interface to the tree-sitter library
77 | 3. Adding new wrapper functions that can handle type variations
78 | 4. Improving Protocol definitions to better match runtime behavior
79 | 
80 | By following these guidelines, we maintain a balance between static type safety and runtime flexibility when working with the tree-sitter library.
81 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/api.py:
--------------------------------------------------------------------------------

```python
  1 | """API functions for accessing container dependencies.
  2 | 
  3 | This module provides function-based access to dependencies managed by the
  4 | container, helping to break circular import chains and simplify access.
  5 | """
  6 | 
  7 | import logging
  8 | from typing import Any, Dict, List, Optional
  9 | 
 10 | from .di import get_container
 11 | from .exceptions import ProjectError
 12 | 
 13 | logger = logging.getLogger(__name__)
 14 | 
 15 | 
 16 | def get_project_registry() -> Any:
 17 |     """Get the project registry."""
 18 |     return get_container().project_registry
 19 | 
 20 | 
 21 | def get_language_registry() -> Any:
 22 |     """Get the language registry."""
 23 |     return get_container().language_registry
 24 | 
 25 | 
 26 | def get_tree_cache() -> Any:
 27 |     """Get the tree cache."""
 28 |     return get_container().tree_cache
 29 | 
 30 | 
 31 | def get_config() -> Any:
 32 |     """Get the current configuration."""
 33 |     return get_container().get_config()
 34 | 
 35 | 
 36 | def get_config_manager() -> Any:
 37 |     """Get the configuration manager."""
 38 |     return get_container().config_manager
 39 | 
 40 | 
 41 | def register_project(path: str, name: Optional[str] = None, description: Optional[str] = None) -> Dict[str, Any]:
 42 |     """Register a project."""
 43 |     project_registry = get_project_registry()
 44 |     language_registry = get_language_registry()
 45 | 
 46 |     try:
 47 |         # Register project
 48 |         project = project_registry.register_project(name or path, path, description)
 49 | 
 50 |         # Scan for languages
 51 |         project.scan_files(language_registry)
 52 | 
 53 |         project_dict = project.to_dict()
 54 |         # Add type annotations
 55 |         result: Dict[str, Any] = {
 56 |             "name": project_dict["name"],
 57 |             "root_path": project_dict["root_path"],
 58 |             "description": project_dict["description"],
 59 |             "languages": project_dict["languages"],
 60 |             "last_scan_time": project_dict["last_scan_time"],
 61 |         }
 62 |         return result
 63 |     except Exception as e:
 64 |         raise ProjectError(f"Failed to register project: {e}") from e
 65 | 
 66 | 
 67 | def list_projects() -> List[Dict[str, Any]]:
 68 |     """List all registered projects."""
 69 |     projects_list = get_project_registry().list_projects()
 70 |     # Convert to explicitly typed list
 71 |     result: List[Dict[str, Any]] = []
 72 |     for project in projects_list:
 73 |         result.append(
 74 |             {
 75 |                 "name": project["name"],
 76 |                 "root_path": project["root_path"],
 77 |                 "description": project["description"],
 78 |                 "languages": project["languages"],
 79 |                 "last_scan_time": project["last_scan_time"],
 80 |             }
 81 |         )
 82 |     return result
 83 | 
 84 | 
 85 | def remove_project(name: str) -> Dict[str, str]:
 86 |     """Remove a registered project."""
 87 |     get_project_registry().remove_project(name)
 88 |     return {"status": "success", "message": f"Project '{name}' removed"}
 89 | 
 90 | 
 91 | def clear_cache(project: Optional[str] = None, file_path: Optional[str] = None) -> Dict[str, str]:
 92 |     """Clear the parse tree cache."""
 93 |     tree_cache = get_tree_cache()
 94 | 
 95 |     if project and file_path:
 96 |         # Get file path
 97 |         project_registry = get_project_registry()
 98 |         project_obj = project_registry.get_project(project)
 99 |         abs_path = project_obj.get_file_path(file_path)
100 | 
101 |         # Clear cache
102 |         tree_cache.invalidate(abs_path)
103 |         return {"status": "success", "message": f"Cache cleared for {file_path} in {project}"}
104 |     else:
105 |         # Clear all
106 |         tree_cache.invalidate()
107 |         return {"status": "success", "message": "Cache cleared"}
108 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/tools/project.py:
--------------------------------------------------------------------------------

```python
  1 | """Project management tools for MCP server."""
  2 | 
  3 | from typing import Any, Dict, List, Optional
  4 | 
  5 | from ..api import get_language_registry, get_project_registry
  6 | from ..exceptions import ProjectError
  7 | 
  8 | 
  9 | def register_project(path: str, name: Optional[str] = None, description: Optional[str] = None) -> Dict[str, Any]:
 10 |     """
 11 |     Register a project for code analysis.
 12 | 
 13 |     Args:
 14 |         path: Path to the project directory
 15 |         name: Optional name for the project (defaults to directory name)
 16 |         description: Optional description
 17 | 
 18 |     Returns:
 19 |         Project information
 20 |     """
 21 |     # Get dependencies from API
 22 |     project_registry = get_project_registry()
 23 |     language_registry = get_language_registry()
 24 | 
 25 |     try:
 26 |         # Register project
 27 |         project = project_registry.register_project(name or path, path, description)
 28 | 
 29 |         # Scan for languages
 30 |         project.scan_files(language_registry)
 31 | 
 32 |         project_dict = project.to_dict()
 33 |         # Add type annotations for clarity
 34 |         result: Dict[str, Any] = {
 35 |             "name": project_dict["name"],
 36 |             "root_path": project_dict["root_path"],
 37 |             "description": project_dict["description"],
 38 |             "languages": project_dict["languages"],
 39 |             "last_scan_time": project_dict["last_scan_time"],
 40 |         }
 41 |         return result
 42 |     except Exception as e:
 43 |         raise ProjectError(f"Failed to register project: {e}") from e
 44 | 
 45 | 
 46 | def get_project(name: str) -> Dict[str, Any]:
 47 |     """
 48 |     Get project information.
 49 | 
 50 |     Args:
 51 |         name: Project name
 52 | 
 53 |     Returns:
 54 |         Project information
 55 |     """
 56 |     # Get dependency from API
 57 |     project_registry = get_project_registry()
 58 | 
 59 |     try:
 60 |         project = project_registry.get_project(name)
 61 |         project_dict = project.to_dict()
 62 |         # Add type annotations for clarity
 63 |         result: Dict[str, Any] = {
 64 |             "name": project_dict["name"],
 65 |             "root_path": project_dict["root_path"],
 66 |             "description": project_dict["description"],
 67 |             "languages": project_dict["languages"],
 68 |             "last_scan_time": project_dict["last_scan_time"],
 69 |         }
 70 |         return result
 71 |     except Exception as e:
 72 |         raise ProjectError(f"Failed to get project: {e}") from e
 73 | 
 74 | 
 75 | def list_projects() -> List[Dict[str, Any]]:
 76 |     """
 77 |     List all registered projects.
 78 | 
 79 |     Returns:
 80 |         List of project information
 81 |     """
 82 |     # Get dependency from API
 83 |     project_registry = get_project_registry()
 84 | 
 85 |     projects_list = project_registry.list_projects()
 86 |     # Explicitly create a typed list
 87 |     result: List[Dict[str, Any]] = []
 88 |     for project in projects_list:
 89 |         result.append(
 90 |             {
 91 |                 "name": project["name"],
 92 |                 "root_path": project["root_path"],
 93 |                 "description": project["description"],
 94 |                 "languages": project["languages"],
 95 |                 "last_scan_time": project["last_scan_time"],
 96 |             }
 97 |         )
 98 |     return result
 99 | 
100 | 
101 | def remove_project(name: str) -> Dict[str, str]:
102 |     """
103 |     Remove a project.
104 | 
105 |     Args:
106 |         name: Project name
107 | 
108 |     Returns:
109 |         Success message
110 |     """
111 |     # Get dependency from API
112 |     project_registry = get_project_registry()
113 | 
114 |     try:
115 |         project_registry.remove_project(name)
116 |         return {"status": "success", "message": f"Project '{name}' removed"}
117 |     except Exception as e:
118 |         raise ProjectError(f"Failed to remove project: {e}") from e
119 | 
```

--------------------------------------------------------------------------------
/tests/test_diagnostics/test_ast.py:
--------------------------------------------------------------------------------

```python
  1 | """Example of using pytest with diagnostic plugin for testing."""
  2 | 
  3 | import tempfile
  4 | from pathlib import Path
  5 | 
  6 | import pytest
  7 | 
  8 | from mcp_server_tree_sitter.api import get_project_registry
  9 | from mcp_server_tree_sitter.language.registry import LanguageRegistry
 10 | from tests.test_helpers import get_ast, register_project_tool
 11 | 
 12 | # Load the diagnostic fixture
 13 | pytest.importorskip("mcp_server_tree_sitter.testing")
 14 | 
 15 | 
 16 | @pytest.fixture
 17 | def test_project():
 18 |     """Create a temporary test project with a sample file."""
 19 |     # Set up a temporary directory
 20 |     with tempfile.TemporaryDirectory() as temp_dir:
 21 |         project_path = Path(temp_dir)
 22 | 
 23 |         # Create a test file
 24 |         test_file = project_path / "test.py"
 25 |         with open(test_file, "w") as f:
 26 |             f.write("def hello():\n    print('Hello, world!')\n\nhello()\n")
 27 | 
 28 |         # Register project
 29 |         project_name = "diagnostic_test_project"
 30 |         register_project_tool(path=str(project_path), name=project_name)
 31 | 
 32 |         # Yield the project info
 33 |         yield {"name": project_name, "path": project_path, "file": "test.py"}
 34 | 
 35 |         # Clean up
 36 |         project_registry = get_project_registry()
 37 |         try:
 38 |             project_registry.remove_project(project_name)
 39 |         except Exception:
 40 |             pass
 41 | 
 42 | 
 43 | @pytest.mark.diagnostic
 44 | def test_ast_failure(test_project, diagnostic) -> None:
 45 |     """Test the get_ast functionality."""
 46 |     # Add test details to diagnostic data
 47 |     diagnostic.add_detail("project", test_project["name"])
 48 |     diagnostic.add_detail("file", test_project["file"])
 49 | 
 50 |     try:
 51 |         # Try to get the AST
 52 |         ast_result = get_ast(
 53 |             project=test_project["name"],
 54 |             path=test_project["file"],
 55 |             max_depth=3,
 56 |             include_text=True,
 57 |         )
 58 | 
 59 |         # Add the result to diagnostics
 60 |         diagnostic.add_detail("ast_result", str(ast_result))
 61 | 
 62 |         # This assertion would fail if there's an issue with AST parsing
 63 |         assert "tree" in ast_result, "AST result should contain a tree"
 64 | 
 65 |         # Check that the tree doesn't contain an error
 66 |         if isinstance(ast_result["tree"], dict) and "error" in ast_result["tree"]:
 67 |             raise AssertionError(f"AST tree contains an error: {ast_result['tree']['error']}")
 68 | 
 69 |     except Exception as e:
 70 |         # Record the error in diagnostics
 71 |         diagnostic.add_error("AstParsingError", str(e))
 72 | 
 73 |         # Create the artifact
 74 |         artifact = {
 75 |             "error_type": type(e).__name__,
 76 |             "error_message": str(e),
 77 |             "project": test_project["name"],
 78 |             "file": test_project["file"],
 79 |         }
 80 |         diagnostic.add_artifact("ast_failure", artifact)
 81 | 
 82 |         # Re-raise to fail the test
 83 |         raise
 84 | 
 85 | 
 86 | @pytest.mark.diagnostic
 87 | def test_language_detection(diagnostic) -> None:
 88 |     """Test language detection functionality."""
 89 |     registry = LanguageRegistry()
 90 | 
 91 |     # Test a few common file extensions
 92 |     test_files = {
 93 |         "test.py": "python",
 94 |         "test.js": "javascript",
 95 |         "test.ts": "typescript",
 96 |         "test.unknown": None,
 97 |     }
 98 | 
 99 |     results = {}
100 |     failures = []
101 | 
102 |     for filename, expected in test_files.items():
103 |         detected = registry.language_for_file(filename)
104 |         match = detected == expected
105 | 
106 |         results[filename] = {"detected": detected, "expected": expected, "match": match}
107 | 
108 |         if not match:
109 |             failures.append(filename)
110 | 
111 |     # Add all results to diagnostic data
112 |     diagnostic.add_detail("detection_results", results)
113 |     if failures:
114 |         diagnostic.add_detail("failed_files", failures)
115 | 
116 |     # Check results with proper assertions
117 |     for filename, expected in test_files.items():
118 |         assert registry.language_for_file(filename) == expected, f"Language detection failed for {filename}"
119 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/tools/ast_operations.py:
--------------------------------------------------------------------------------

```python
  1 | """AST operation tools for MCP server."""
  2 | 
  3 | import logging
  4 | from typing import Any, Dict, Optional
  5 | 
  6 | from ..exceptions import FileAccessError, ParsingError
  7 | from ..models.ast import node_to_dict
  8 | from ..utils.file_io import read_binary_file
  9 | from ..utils.security import validate_file_access
 10 | from ..utils.tree_sitter_helpers import (
 11 |     parse_source,
 12 | )
 13 | 
 14 | logger = logging.getLogger(__name__)
 15 | 
 16 | 
 17 | def get_file_ast(
 18 |     project: Any,
 19 |     path: str,
 20 |     language_registry: Any,
 21 |     tree_cache: Any,
 22 |     max_depth: Optional[int] = None,
 23 |     include_text: bool = True,
 24 | ) -> Dict[str, Any]:
 25 |     """
 26 |     Get the AST for a file.
 27 | 
 28 |     Args:
 29 |         project: Project object
 30 |         path: File path (relative to project root)
 31 |         language_registry: Language registry
 32 |         tree_cache: Tree cache instance
 33 |         max_depth: Maximum depth to traverse the tree
 34 |         include_text: Whether to include node text
 35 | 
 36 |     Returns:
 37 |         AST as a nested dictionary
 38 | 
 39 |     Raises:
 40 |         FileAccessError: If file access fails
 41 |         ParsingError: If parsing fails
 42 |     """
 43 |     abs_path = project.get_file_path(path)
 44 | 
 45 |     try:
 46 |         validate_file_access(abs_path, project.root_path)
 47 |     except Exception as e:
 48 |         raise FileAccessError(f"Access denied: {e}") from e
 49 | 
 50 |     language = language_registry.language_for_file(path)
 51 |     if not language:
 52 |         raise ParsingError(f"Could not detect language for {path}")
 53 | 
 54 |     tree, source_bytes = parse_file(abs_path, language, language_registry, tree_cache)
 55 | 
 56 |     return {
 57 |         "file": path,
 58 |         "language": language,
 59 |         "tree": node_to_dict(
 60 |             tree.root_node,
 61 |             source_bytes,
 62 |             include_children=True,
 63 |             include_text=include_text,
 64 |             max_depth=max_depth if max_depth is not None else 5,
 65 |         ),
 66 |     }
 67 | 
 68 | 
 69 | def parse_file(file_path: Any, language: str, language_registry: Any, tree_cache: Any) -> tuple[Any, bytes]:
 70 |     """
 71 |     Parse a file using tree-sitter.
 72 | 
 73 |     Args:
 74 |         file_path: Path to file
 75 |         language: Language identifier
 76 |         language_registry: Language registry
 77 |         tree_cache: Tree cache instance
 78 | 
 79 |     Returns:
 80 |         (Tree, source_bytes) tuple
 81 | 
 82 |     Raises:
 83 |         ParsingError: If parsing fails
 84 |     """
 85 |     # Always check the cache first, even if caching is disabled
 86 |     # This ensures cache misses are tracked correctly in tests
 87 |     cached = tree_cache.get(file_path, language)
 88 |     if cached:
 89 |         tree, bytes_data = cached
 90 |         return tree, bytes_data
 91 | 
 92 |     try:
 93 |         # Parse the file using helper
 94 |         parser = language_registry.get_parser(language)
 95 |         # Use source directly with parser to avoid parser vs. language confusion
 96 |         source_bytes = read_binary_file(file_path)
 97 |         tree = parse_source(source_bytes, parser)
 98 |         result_tuple = (tree, source_bytes)
 99 | 
100 |         # Cache the tree only if caching is enabled
101 |         is_cache_enabled = False
102 |         try:
103 |             # Get cache enabled state from tree_cache
104 |             is_cache_enabled = tree_cache._is_cache_enabled()
105 |         except Exception:
106 |             # Fallback to instance value if method not available
107 |             is_cache_enabled = getattr(tree_cache, "enabled", False)
108 | 
109 |         # Store in cache only if enabled
110 |         if is_cache_enabled:
111 |             tree_cache.put(file_path, language, tree, source_bytes)
112 | 
113 |         return result_tuple
114 |     except Exception as e:
115 |         raise ParsingError(f"Error parsing {file_path}: {e}") from e
116 | 
117 | 
118 | def find_node_at_position(root_node: Any, row: int, column: int) -> Optional[Any]:
119 |     """
120 |     Find the most specific node at a given position.
121 | 
122 |     Args:
123 |         root_node: Root node to search from
124 |         row: Row (line) number, 0-based
125 |         column: Column number, 0-based
126 | 
127 |     Returns:
128 |         Node at position or None if not found
129 |     """
130 |     from ..models.ast import find_node_at_position as find_node
131 | 
132 |     return find_node(root_node, row, column)
133 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/file_io.py:
--------------------------------------------------------------------------------

```python
  1 | """Utilities for safe file operations.
  2 | 
  3 | This module provides safe file I/O operations with proper encoding handling
  4 | and consistent interfaces for both text and binary operations.
  5 | """
  6 | 
  7 | from pathlib import Path
  8 | from typing import List, Optional, Tuple, Union
  9 | 
 10 | 
 11 | def read_text_file(path: Union[str, Path]) -> List[str]:
 12 |     """
 13 |     Safely read a text file with proper encoding handling.
 14 | 
 15 |     Args:
 16 |         path: Path to the file
 17 | 
 18 |     Returns:
 19 |         List of lines from the file
 20 |     """
 21 |     with open(str(path), "r", encoding="utf-8", errors="replace") as f:
 22 |         return f.readlines()
 23 | 
 24 | 
 25 | def read_binary_file(path: Union[str, Path]) -> bytes:
 26 |     """
 27 |     Safely read a binary file.
 28 | 
 29 |     Args:
 30 |         path: Path to the file
 31 | 
 32 |     Returns:
 33 |         File contents as bytes
 34 |     """
 35 |     with open(str(path), "rb") as f:
 36 |         return f.read()
 37 | 
 38 | 
 39 | def get_file_content_and_lines(path: Union[str, Path]) -> Tuple[bytes, List[str]]:
 40 |     """
 41 |     Get both binary content and text lines from a file.
 42 | 
 43 |     Args:
 44 |         path: Path to the file
 45 | 
 46 |     Returns:
 47 |         Tuple of (binary_content, text_lines)
 48 |     """
 49 |     binary_content = read_binary_file(path)
 50 |     text_lines = read_text_file(path)
 51 |     return binary_content, text_lines
 52 | 
 53 | 
 54 | def is_line_comment(line: str, comment_prefix: str) -> bool:
 55 |     """
 56 |     Check if a line is a comment.
 57 | 
 58 |     Args:
 59 |         line: The line to check
 60 |         comment_prefix: Comment prefix character(s)
 61 | 
 62 |     Returns:
 63 |         True if the line is a comment
 64 |     """
 65 |     return line.strip().startswith(comment_prefix)
 66 | 
 67 | 
 68 | def count_comment_lines(lines: List[str], comment_prefix: str) -> int:
 69 |     """
 70 |     Count comment lines in a file.
 71 | 
 72 |     Args:
 73 |         lines: List of lines to check
 74 |         comment_prefix: Comment prefix character(s)
 75 | 
 76 |     Returns:
 77 |         Number of comment lines
 78 |     """
 79 |     return sum(1 for line in lines if is_line_comment(line, comment_prefix))
 80 | 
 81 | 
 82 | def get_comment_prefix(language: str) -> Optional[str]:
 83 |     """
 84 |     Get the comment prefix for a language.
 85 | 
 86 |     Args:
 87 |         language: Language identifier
 88 | 
 89 |     Returns:
 90 |         Comment prefix or None if unknown
 91 |     """
 92 |     # Language-specific comment detection
 93 |     comment_starters = {
 94 |         "python": "#",
 95 |         "javascript": "//",
 96 |         "typescript": "//",
 97 |         "java": "//",
 98 |         "c": "//",
 99 |         "cpp": "//",
100 |         "go": "//",
101 |         "ruby": "#",
102 |         "rust": "//",
103 |         "php": "//",
104 |         "swift": "//",
105 |         "kotlin": "//",
106 |         "scala": "//",
107 |         "bash": "#",
108 |         "shell": "#",
109 |         "yaml": "#",
110 |         "html": "<!--",
111 |         "css": "/*",
112 |         "scss": "//",
113 |         "sass": "//",
114 |         "sql": "--",
115 |     }
116 | 
117 |     return comment_starters.get(language)
118 | 
119 | 
120 | def parse_file_with_encoding(path: Union[str, Path], encoding: str = "utf-8") -> Tuple[bytes, List[str]]:
121 |     """
122 |     Parse a file with explicit encoding handling, returning both binary and text.
123 | 
124 |     Args:
125 |         path: Path to the file
126 |         encoding: Text encoding to use
127 | 
128 |     Returns:
129 |         Tuple of (binary_content, decoded_lines)
130 |     """
131 |     binary_content = read_binary_file(path)
132 | 
133 |     # Now decode the binary content with the specified encoding
134 |     text = binary_content.decode(encoding, errors="replace")
135 |     lines = text.splitlines(True)  # Keep line endings
136 | 
137 |     return binary_content, lines
138 | 
139 | 
140 | def read_file_lines(path: Union[str, Path], start_line: int = 0, max_lines: Optional[int] = None) -> List[str]:
141 |     """
142 |     Read specific lines from a file.
143 | 
144 |     Args:
145 |         path: Path to the file
146 |         start_line: First line to include (0-based)
147 |         max_lines: Maximum number of lines to return
148 | 
149 |     Returns:
150 |         List of requested lines
151 |     """
152 |     with open(str(path), "r", encoding="utf-8", errors="replace") as f:
153 |         # Skip lines before start_line
154 |         for _ in range(start_line):
155 |             next(f, None)
156 | 
157 |         # Read up to max_lines
158 |         if max_lines is not None:
159 |             return [f.readline() for _ in range(max_lines)]
160 |         else:
161 |             return f.readlines()
162 | 
```

--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Tree-sitter Server: TODO Board
  2 | 
  3 | This Kanban board tracks tasks specifically focused on improving partially working commands and implementing missing features.
  4 | 
  5 | ## In Progress
  6 | 
  7 | ### High Priority
  8 | ---
  9 | 
 10 | #### Fix Similar Code Detection
 11 | - **Description**: Improve the `find_similar_code` command to reliably return results
 12 | - **Tasks**:
 13 |   - [ ] Debug why command completes but doesn't return results
 14 |   - [ ] Optimize similarity threshold and matching algorithm
 15 |   - [ ] Add more detailed logging for troubleshooting
 16 |   - [ ] Create comprehensive test cases with expected results
 17 | - **Acceptance Criteria**:
 18 |   - Command reliably returns similar code snippets when they exist
 19 |   - Appropriate feedback when no similar code is found
 20 |   - Documentation updated with examples and recommended thresholds
 21 | - **Complexity**: Medium
 22 | - **Dependencies**: None
 23 | 
 24 | #### Complete Tree Editing and Incremental Parsing
 25 | - **Description**: Extend AST functionality to support tree manipulation
 26 | - **Tasks**:
 27 |   - [ ] Implement tree editing operations (insert, delete, replace nodes)
 28 |   - [ ] Add incremental parsing to efficiently update trees after edits
 29 |   - [ ] Ensure node IDs remain consistent during tree manipulations
 30 | - **Acceptance Criteria**:
 31 |   - Trees can be modified through API calls
 32 |   - Incremental parsing reduces parse time for small changes
 33 |   - Proper error handling for invalid modifications
 34 | - **Complexity**: High
 35 | - **Dependencies**: None
 36 | 
 37 | ### Medium Priority
 38 | ---
 39 | 
 40 | #### Implement UTF-16 Support
 41 | - **Description**: Add encoding detection and support for UTF-16
 42 | - **Tasks**:
 43 |   - [ ] Implement encoding detection for input files
 44 |   - [ ] Add UTF-16 to UTF-8 conversion for parser compatibility
 45 |   - [ ] Handle position mapping between different encodings
 46 | - **Acceptance Criteria**:
 47 |   - Correctly parse and handle UTF-16 encoded files
 48 |   - Maintain accurate position information in different encodings
 49 |   - Test suite includes UTF-16 encoded files
 50 | - **Complexity**: Medium
 51 | - **Dependencies**: None
 52 | 
 53 | #### Add Read Callable Support
 54 | - **Description**: Implement custom read strategies for efficient large file handling
 55 | - **Tasks**:
 56 |   - [ ] Create streaming parser interface for large files
 57 |   - [ ] Implement memory-efficient parsing strategy
 58 |   - [ ] Add support for custom read handlers
 59 | - **Acceptance Criteria**:
 60 |   - Successfully parse files larger than memory constraints
 61 |   - Performance tests show acceptable parsing speed
 62 |   - Documentation on how to use custom read strategies
 63 | - **Complexity**: High
 64 | - **Dependencies**: None
 65 | 
 66 | ## Ready for Review
 67 | 
 68 | ### High Priority
 69 | ---
 70 | 
 71 | #### Complete MCP Context Progress Reporting
 72 | - **Description**: Implement progress reporting for long-running operations
 73 | - **Tasks**:
 74 |   - [ ] Add progress tracking to all long-running operations
 75 |   - [ ] Implement progress callbacks in the MCP context
 76 |   - [ ] Update API to report progress percentage
 77 | - **Acceptance Criteria**:
 78 |   - Long-running operations report progress
 79 |   - Progress is visible to the user
 80 |   - Cancellation is possible for operations in progress
 81 | - **Complexity**: Low
 82 | - **Dependencies**: None
 83 | 
 84 | ## Done
 85 | 
 86 | *No tasks completed yet*
 87 | 
 88 | ## Backlog
 89 | 
 90 | ### Low Priority
 91 | ---
 92 | 
 93 | #### Add Image Handling Support
 94 | - **Description**: Implement support for returning images/visualizations from tools
 95 | - **Tasks**:
 96 |   - [ ] Create image generation utilities for AST visualization
 97 |   - [ ] Add support for returning images in MCP responses
 98 |   - [ ] Implement SVG or PNG export of tree structures
 99 | - **Acceptance Criteria**:
100 |   - Tools can return visual representations of code structures
101 |   - AST visualizations can be generated and returned
102 | - **Complexity**: Medium
103 | - **Dependencies**: None
104 | 
105 | ---
106 | 
107 | ## Task Metadata
108 | 
109 | ### Priority Levels
110 | - **High**: Critical for core functionality, should be addressed immediately
111 | - **Medium**: Important for comprehensive feature set, address after high priority items
112 | - **Low**: Nice to have, address when resources permit
113 | 
114 | ### Complexity Levels
115 | - **Low**: Estimated 1-2 days of work
116 | - **Medium**: Estimated 3-5 days of work
117 | - **High**: Estimated 1-2 weeks of work
118 | 
```

--------------------------------------------------------------------------------
/tests/test_config_manager.py:
--------------------------------------------------------------------------------

```python
  1 | """Tests for the new ConfigurationManager class."""
  2 | 
  3 | import os
  4 | import tempfile
  5 | 
  6 | import pytest
  7 | import yaml
  8 | 
  9 | # Import will fail initially until we implement the class
 10 | 
 11 | 
 12 | @pytest.fixture
 13 | def temp_yaml_file():
 14 |     """Create a temporary YAML file with test configuration."""
 15 |     with tempfile.NamedTemporaryFile(suffix=".yaml", mode="w+", delete=False) as temp_file:
 16 |         test_config = {
 17 |             "cache": {"enabled": True, "max_size_mb": 256, "ttl_seconds": 3600},
 18 |             "security": {"max_file_size_mb": 10, "excluded_dirs": [".git", "node_modules", "__pycache__", ".cache"]},
 19 |             "language": {"auto_install": True, "default_max_depth": 7},
 20 |         }
 21 |         yaml.dump(test_config, temp_file)
 22 |         temp_file.flush()
 23 |         temp_file_path = temp_file.name
 24 | 
 25 |     yield temp_file_path
 26 | 
 27 |     # Clean up
 28 |     os.unlink(temp_file_path)
 29 | 
 30 | 
 31 | def test_config_manager_initialization():
 32 |     """Test that ConfigurationManager initializes with default config."""
 33 |     # This test will fail until we implement ConfigurationManager
 34 |     from mcp_server_tree_sitter.config import ConfigurationManager
 35 | 
 36 |     manager = ConfigurationManager()
 37 |     config = manager.get_config()
 38 | 
 39 |     # Check default values
 40 |     assert config.cache.max_size_mb == 100
 41 |     assert config.security.max_file_size_mb == 5
 42 |     assert config.language.default_max_depth == 5
 43 | 
 44 | 
 45 | def test_config_manager_load_from_file(temp_yaml_file):
 46 |     """Test loading configuration from a file."""
 47 |     # This test will fail until we implement ConfigurationManager
 48 |     from mcp_server_tree_sitter.config import ConfigurationManager
 49 | 
 50 |     manager = ConfigurationManager()
 51 |     manager.load_from_file(temp_yaml_file)
 52 |     config = manager.get_config()
 53 | 
 54 |     # Check loaded values
 55 |     assert config.cache.max_size_mb == 256
 56 |     assert config.security.max_file_size_mb == 10
 57 |     assert config.language.default_max_depth == 7
 58 | 
 59 | 
 60 | def test_config_manager_update_values():
 61 |     """Test updating individual configuration values."""
 62 |     # This test will fail until we implement ConfigurationManager
 63 |     from mcp_server_tree_sitter.config import ConfigurationManager
 64 | 
 65 |     manager = ConfigurationManager()
 66 | 
 67 |     # Update values
 68 |     manager.update_value("cache.max_size_mb", 512)
 69 |     manager.update_value("security.max_file_size_mb", 20)
 70 | 
 71 |     # Check updated values
 72 |     config = manager.get_config()
 73 |     assert config.cache.max_size_mb == 512
 74 |     assert config.security.max_file_size_mb == 20
 75 | 
 76 | 
 77 | def test_config_manager_to_dict():
 78 |     """Test converting configuration to dictionary."""
 79 |     # This test will fail until we implement ConfigurationManager
 80 |     from mcp_server_tree_sitter.config import ConfigurationManager
 81 | 
 82 |     manager = ConfigurationManager()
 83 |     config_dict = manager.to_dict()
 84 | 
 85 |     # Check dictionary structure
 86 |     assert "cache" in config_dict
 87 |     assert "security" in config_dict
 88 |     assert "language" in config_dict
 89 |     assert config_dict["cache"]["max_size_mb"] == 100
 90 | 
 91 | 
 92 | def test_env_overrides_defaults(monkeypatch):
 93 |     """Environment variables should override hard-coded defaults."""
 94 |     monkeypatch.setenv("MCP_TS_CACHE_MAX_SIZE_MB", "512")
 95 | 
 96 |     from mcp_server_tree_sitter.config import ConfigurationManager
 97 | 
 98 |     mgr = ConfigurationManager()
 99 |     cfg = mgr.get_config()
100 | 
101 |     assert cfg.cache.max_size_mb == 512, "Environment variable should override default value"
102 |     # ensure other defaults stay intact
103 |     assert cfg.security.max_file_size_mb == 5
104 |     assert cfg.language.default_max_depth == 5
105 | 
106 | 
107 | def test_env_overrides_yaml(temp_yaml_file, monkeypatch):
108 |     """Environment variables should take precedence over YAML values."""
109 |     # YAML sets 256; env var must win with 1024
110 |     monkeypatch.setenv("MCP_TS_CACHE_MAX_SIZE_MB", "1024")
111 |     monkeypatch.setenv("MCP_TS_SECURITY_MAX_FILE_SIZE_MB", "15")
112 | 
113 |     from mcp_server_tree_sitter.config import ConfigurationManager
114 | 
115 |     mgr = ConfigurationManager()
116 |     mgr.load_from_file(temp_yaml_file)
117 |     cfg = mgr.get_config()
118 | 
119 |     assert cfg.cache.max_size_mb == 1024, "Environment variable should override YAML value"
120 |     assert cfg.security.max_file_size_mb == 15, "Environment variable should override YAML value"
121 | 
```

--------------------------------------------------------------------------------
/tests/test_logging_early_init.py:
--------------------------------------------------------------------------------

```python
  1 | """Test that logging configuration is applied early in application lifecycle."""
  2 | 
  3 | import importlib
  4 | import logging
  5 | import os
  6 | from unittest.mock import MagicMock, patch
  7 | 
  8 | 
  9 | def test_early_init_in_package():
 10 |     """Test that logging is configured before other modules are imported."""
 11 |     # Rather than mocking which won't work well with imports,
 12 |     # we'll check the actual package __init__.py file content
 13 |     import inspect
 14 | 
 15 |     import mcp_server_tree_sitter
 16 | 
 17 |     # Get the source code of the package __init__.py
 18 |     init_source = inspect.getsource(mcp_server_tree_sitter)
 19 | 
 20 |     # Verify bootstrap import is present and comes before other imports
 21 |     assert "from . import bootstrap" in init_source, "bootstrap should be imported in __init__.py"
 22 | 
 23 |     # Check the bootstrap/__init__.py to ensure it imports logging_bootstrap
 24 |     import mcp_server_tree_sitter.bootstrap
 25 | 
 26 |     bootstrap_init_source = inspect.getsource(mcp_server_tree_sitter.bootstrap)
 27 | 
 28 |     assert "from . import logging_bootstrap" in bootstrap_init_source, "bootstrap init should import logging_bootstrap"
 29 | 
 30 |     # Check that bootstrap's __all__ includes logging functions
 31 |     assert "get_logger" in mcp_server_tree_sitter.bootstrap.__all__, "get_logger should be exported by bootstrap"
 32 |     assert "update_log_levels" in mcp_server_tree_sitter.bootstrap.__all__, (
 33 |         "update_log_levels should be exported by bootstrap"
 34 |     )
 35 | 
 36 | 
 37 | def test_configure_is_called_at_import():
 38 |     """Test that the configure_root_logger is called when bootstrap is imported."""
 39 |     # Mock the root logger configuration function
 40 |     with patch("logging.basicConfig") as mock_basic_config:
 41 |         # Force reload of the module to trigger initialization
 42 |         import mcp_server_tree_sitter.bootstrap.logging_bootstrap
 43 | 
 44 |         importlib.reload(mcp_server_tree_sitter.bootstrap.logging_bootstrap)
 45 | 
 46 |         # Verify logging.basicConfig was called
 47 |         mock_basic_config.assert_called_once()
 48 | 
 49 | 
 50 | def test_environment_vars_processed_early():
 51 |     """Test that environment variables are processed before logger configuration."""
 52 |     # Test the function directly rather than trying to mock it
 53 |     # Save current environment variable value
 54 |     original_env = os.environ.get("MCP_TS_LOG_LEVEL", None)
 55 | 
 56 |     try:
 57 |         # Test with DEBUG level
 58 |         os.environ["MCP_TS_LOG_LEVEL"] = "DEBUG"
 59 |         from mcp_server_tree_sitter.bootstrap.logging_bootstrap import get_log_level_from_env
 60 | 
 61 |         # Verify function returns correct level
 62 |         assert get_log_level_from_env() == logging.DEBUG, "Should return DEBUG level from environment"
 63 | 
 64 |         # Test with INFO level - this time specify module differently to avoid NameError
 65 |         os.environ["MCP_TS_LOG_LEVEL"] = "INFO"
 66 |         # First import the module
 67 |         import importlib
 68 | 
 69 |         import mcp_server_tree_sitter.bootstrap.logging_bootstrap as bootstrap_logging
 70 | 
 71 |         # Then reload it to pick up the new environment variable
 72 |         importlib.reload(bootstrap_logging)
 73 | 
 74 |         # Verify the function returns the new level
 75 |         assert bootstrap_logging.get_log_level_from_env() == logging.INFO, "Should return INFO level from environment"
 76 | 
 77 |     finally:
 78 |         # Restore environment
 79 |         if original_env is None:
 80 |             del os.environ["MCP_TS_LOG_LEVEL"]
 81 |         else:
 82 |             os.environ["MCP_TS_LOG_LEVEL"] = original_env
 83 | 
 84 | 
 85 | def test_handlers_synchronized_at_init():
 86 |     """Test that handler levels are synchronized at initialization."""
 87 |     # Mock handlers on the root logger
 88 |     mock_handler = MagicMock()
 89 |     root_logger = logging.getLogger()
 90 |     original_handlers = root_logger.handlers
 91 | 
 92 |     try:
 93 |         # Add mock handler and capture original handlers
 94 |         root_logger.handlers = [mock_handler]
 95 | 
 96 |         # Set environment variable
 97 |         with patch.dict(os.environ, {"MCP_TS_LOG_LEVEL": "DEBUG"}):
 98 |             # Mock the get_log_level_from_env function to control return value
 99 |             with patch("mcp_server_tree_sitter.bootstrap.logging_bootstrap.get_log_level_from_env") as mock_get_level:
100 |                 mock_get_level.return_value = logging.DEBUG
101 | 
102 |                 # Force reload to trigger initialization
103 |                 import mcp_server_tree_sitter.bootstrap.logging_bootstrap
104 | 
105 |                 importlib.reload(mcp_server_tree_sitter.bootstrap.logging_bootstrap)
106 | 
107 |                 # Verify handler level was set
108 |                 mock_handler.setLevel.assert_called_with(logging.DEBUG)
109 |     finally:
110 |         # Restore original handlers
111 |         root_logger.handlers = original_handlers
112 | 
```

--------------------------------------------------------------------------------
/tests/test_diagnostics/test_language_registry.py:
--------------------------------------------------------------------------------

```python
  1 | """Pytest-based diagnostic tests for language registry functionality."""
  2 | 
  3 | import pytest
  4 | 
  5 | from mcp_server_tree_sitter.language.registry import LanguageRegistry
  6 | 
  7 | 
  8 | @pytest.mark.diagnostic
  9 | def test_language_detection(diagnostic) -> None:
 10 |     """Test language detection functionality."""
 11 |     registry = LanguageRegistry()
 12 | 
 13 |     # Test a few common file extensions
 14 |     test_files = {
 15 |         "test.py": "python",
 16 |         "test.js": "javascript",
 17 |         "test.ts": "typescript",
 18 |         "test.go": "go",
 19 |         "test.cpp": "cpp",
 20 |         "test.c": "c",
 21 |         "test.rs": "rust",
 22 |         "test.unknown": None,
 23 |     }
 24 | 
 25 |     results = {}
 26 |     failures = []
 27 | 
 28 |     for filename, expected in test_files.items():
 29 |         detected = registry.language_for_file(filename)
 30 |         match = detected == expected
 31 | 
 32 |         results[filename] = {"detected": detected, "expected": expected, "match": match}
 33 | 
 34 |         if not match:
 35 |             failures.append(filename)
 36 | 
 37 |     # Add all results to diagnostic data
 38 |     diagnostic.add_detail("detection_results", results)
 39 |     if failures:
 40 |         diagnostic.add_detail("failed_files", failures)
 41 | 
 42 |     # Check results with proper assertions
 43 |     for filename, expected in test_files.items():
 44 |         assert registry.language_for_file(filename) == expected, f"Language detection failed for {filename}"
 45 | 
 46 | 
 47 | @pytest.mark.diagnostic
 48 | def test_language_list_empty(diagnostic) -> None:
 49 |     """Test that list_languages returns languages correctly."""
 50 |     registry = LanguageRegistry()
 51 | 
 52 |     # Get available languages
 53 |     available_languages = registry.list_available_languages()
 54 |     installable_languages = registry.list_installable_languages()
 55 | 
 56 |     # Add results to diagnostic data
 57 |     diagnostic.add_detail("available_languages", available_languages)
 58 |     diagnostic.add_detail("installable_languages", installable_languages)
 59 | 
 60 |     # Check for common languages we expect to be available
 61 |     expected_languages = [
 62 |         "python",
 63 |         "javascript",
 64 |         "typescript",
 65 |         "c",
 66 |         "cpp",
 67 |         "go",
 68 |         "rust",
 69 |     ]
 70 |     for lang in expected_languages:
 71 |         if lang not in available_languages:
 72 |             diagnostic.add_error(
 73 |                 "LanguageNotAvailable",
 74 |                 f"Expected language {lang} not in available languages",
 75 |             )
 76 | 
 77 |     # Assert that some languages are available
 78 |     assert len(available_languages) > 0, "No languages available"
 79 | 
 80 |     # Assert that we find at least some of our expected languages
 81 |     common_languages = set(expected_languages) & set(available_languages)
 82 |     assert len(common_languages) > 0, "None of the expected languages are available"
 83 | 
 84 | 
 85 | @pytest.mark.diagnostic
 86 | def test_language_detection_vs_listing(diagnostic) -> None:
 87 |     """Test discrepancy between language detection and language listing."""
 88 |     registry = LanguageRegistry()
 89 | 
 90 |     # Test with a few common languages
 91 |     test_languages = [
 92 |         "python",
 93 |         "javascript",
 94 |         "typescript",
 95 |         "c",
 96 |         "cpp",
 97 |         "go",
 98 |         "rust",
 99 |     ]
100 | 
101 |     results = {}
102 |     for lang in test_languages:
103 |         try:
104 |             # Check if language is available
105 |             if registry.is_language_available(lang):
106 |                 results[lang] = {
107 |                     "available": True,
108 |                     "language_object": bool(registry.get_language(lang) is not None),
109 |                     "reason": "",
110 |                 }
111 |             else:
112 |                 results[lang] = {
113 |                     "available": False,
114 |                     "reason": "Not available in language-pack",
115 |                     "language_object": False,
116 |                 }
117 |         except Exception as e:
118 |             results[lang] = {"available": False, "error": str(e), "language_object": False}
119 | 
120 |     # Check if languages reported as available appear in list_languages
121 |     available_languages = registry.list_available_languages()
122 | 
123 |     # Add results to diagnostic data
124 |     diagnostic.add_detail("language_results", results)
125 |     diagnostic.add_detail("available_languages", available_languages)
126 | 
127 |     # Compare detection vs listing
128 |     discrepancies = []
129 |     for lang, result in results.items():
130 |         if result.get("available", False) and lang not in available_languages:
131 |             discrepancies.append(lang)
132 | 
133 |     if discrepancies:
134 |         diagnostic.add_error(
135 |             "LanguageInconsistency",
136 |             f"Languages available but not in list_languages: {discrepancies}",
137 |         )
138 | 
139 |     # For diagnostic purposes, not all assertions should fail
140 |     # This checks if there are any available languages
141 |     successful_languages = [lang for lang, result in results.items() if result.get("available", False)]
142 | 
143 |     assert len(successful_languages) > 0, "No languages could be successfully installed"
144 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/context.py:
--------------------------------------------------------------------------------

```python
  1 | """Context class for managing dependency injection.
  2 | 
  3 | This module provides a ServerContext class to manage dependencies
  4 | and provide a cleaner interface for interacting with the application's
  5 | components while supporting dependency injection.
  6 | """
  7 | 
  8 | from typing import Any, Dict, List, Optional
  9 | 
 10 | # Import logging from bootstrap package
 11 | from .bootstrap import get_logger, update_log_levels
 12 | from .cache.parser_cache import TreeCache
 13 | from .config import ConfigurationManager, ServerConfig
 14 | from .di import get_container
 15 | from .exceptions import ProjectError
 16 | from .language.registry import LanguageRegistry
 17 | from .models.project import ProjectRegistry
 18 | 
 19 | logger = get_logger(__name__)
 20 | 
 21 | 
 22 | class ServerContext:
 23 |     """Context for managing application state with dependency injection."""
 24 | 
 25 |     def __init__(
 26 |         self,
 27 |         config_manager: Optional[ConfigurationManager] = None,
 28 |         project_registry: Optional[ProjectRegistry] = None,
 29 |         language_registry: Optional[LanguageRegistry] = None,
 30 |         tree_cache: Optional[TreeCache] = None,
 31 |     ):
 32 |         """
 33 |         Initialize with optional components.
 34 | 
 35 |         If components are not provided, they will be fetched from the global container.
 36 |         """
 37 |         container = get_container()
 38 |         self.config_manager = config_manager or container.config_manager
 39 |         self.project_registry = project_registry or container.project_registry
 40 |         self.language_registry = language_registry or container.language_registry
 41 |         self.tree_cache = tree_cache or container.tree_cache
 42 | 
 43 |     def get_config(self) -> ServerConfig:
 44 |         """Get the current configuration."""
 45 |         return self.config_manager.get_config()
 46 | 
 47 |     # Project management methods
 48 |     def register_project(
 49 |         self, path: str, name: Optional[str] = None, description: Optional[str] = None
 50 |     ) -> Dict[str, Any]:
 51 |         """Register a project for code analysis."""
 52 |         try:
 53 |             # Register project
 54 |             project = self.project_registry.register_project(name or path, path, description)
 55 | 
 56 |             # Scan for languages
 57 |             project.scan_files(self.language_registry)
 58 | 
 59 |             return project.to_dict()
 60 |         except Exception as e:
 61 |             raise ProjectError(f"Failed to register project: {e}") from e
 62 | 
 63 |     def list_projects(self) -> List[Dict[str, Any]]:
 64 |         """List all registered projects."""
 65 |         return self.project_registry.list_projects()
 66 | 
 67 |     def remove_project(self, name: str) -> Dict[str, str]:
 68 |         """Remove a registered project."""
 69 |         self.project_registry.remove_project(name)
 70 |         return {"status": "success", "message": f"Project '{name}' removed"}
 71 | 
 72 |     # Cache management methods
 73 |     def clear_cache(self, project: Optional[str] = None, file_path: Optional[str] = None) -> Dict[str, str]:
 74 |         """Clear the parse tree cache."""
 75 |         if project and file_path:
 76 |             # Get file path
 77 |             project_obj = self.project_registry.get_project(project)
 78 |             abs_path = project_obj.get_file_path(file_path)
 79 | 
 80 |             # Clear cache
 81 |             self.tree_cache.invalidate(abs_path)
 82 |             return {"status": "success", "message": f"Cache cleared for {file_path} in {project}"}
 83 |         else:
 84 |             # Clear all
 85 |             self.tree_cache.invalidate()
 86 |             return {"status": "success", "message": "Cache cleared"}
 87 | 
 88 |     # Configuration management methods
 89 |     def configure(
 90 |         self,
 91 |         config_path: Optional[str] = None,
 92 |         cache_enabled: Optional[bool] = None,
 93 |         max_file_size_mb: Optional[int] = None,
 94 |         log_level: Optional[str] = None,
 95 |     ) -> Dict[str, Any]:
 96 |         """Configure the server."""
 97 |         # Load config if path provided
 98 |         if config_path:
 99 |             logger.info(f"Configuring server with YAML config from: {config_path}")
100 |             self.config_manager.load_from_file(config_path)
101 | 
102 |         # Update specific settings
103 |         if cache_enabled is not None:
104 |             logger.info(f"Setting cache.enabled to {cache_enabled}")
105 |             self.config_manager.update_value("cache.enabled", cache_enabled)
106 |             self.tree_cache.set_enabled(cache_enabled)
107 | 
108 |         if max_file_size_mb is not None:
109 |             logger.info(f"Setting security.max_file_size_mb to {max_file_size_mb}")
110 |             self.config_manager.update_value("security.max_file_size_mb", max_file_size_mb)
111 | 
112 |         if log_level is not None:
113 |             logger.info(f"Setting log_level to {log_level}")
114 |             self.config_manager.update_value("log_level", log_level)
115 | 
116 |             # Apply log level using centralized bootstrap function
117 |             update_log_levels(log_level)
118 |             logger.debug(f"Applied log level {log_level} to mcp_server_tree_sitter loggers")
119 | 
120 |         # Return current config as dict
121 |         return self.config_manager.to_dict()
122 | 
123 | 
124 | # Create a global context instance for convenience
125 | global_context = ServerContext()
126 | 
127 | 
128 | def get_global_context() -> ServerContext:
129 |     """Get the global server context."""
130 |     return global_context
131 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/bootstrap/logging_bootstrap.py:
--------------------------------------------------------------------------------

```python
  1 | """Bootstrap module for logging configuration with minimal dependencies.
  2 | 
  3 | This module is imported first in the initialization sequence to ensure logging
  4 | is configured before any other modules are imported. It has no dependencies
  5 | on other modules in the project to avoid import cycles.
  6 | 
  7 | This is the CANONICAL implementation of logging configuration. If you need to
  8 | modify how logging is configured, make changes here and nowhere else.
  9 | """
 10 | 
 11 | import logging
 12 | import os
 13 | from typing import Dict, Union
 14 | 
 15 | # Numeric values corresponding to log level names
 16 | LOG_LEVEL_MAP: Dict[str, int] = {
 17 |     "DEBUG": logging.DEBUG,
 18 |     "INFO": logging.INFO,
 19 |     "WARNING": logging.WARNING,
 20 |     "ERROR": logging.ERROR,
 21 |     "CRITICAL": logging.CRITICAL,
 22 | }
 23 | 
 24 | 
 25 | def get_log_level_from_env() -> int:
 26 |     """
 27 |     Get log level from environment variable MCP_TS_LOG_LEVEL.
 28 | 
 29 |     Returns:
 30 |         int: Logging level value (e.g., logging.DEBUG, logging.INFO)
 31 |     """
 32 |     env_level = os.environ.get("MCP_TS_LOG_LEVEL", "INFO").upper()
 33 |     return LOG_LEVEL_MAP.get(env_level, logging.INFO)
 34 | 
 35 | 
 36 | def configure_root_logger() -> None:
 37 |     """
 38 |     Configure the root logger based on environment variables.
 39 |     This should be called at the earliest possible point in the application.
 40 |     """
 41 |     log_level = get_log_level_from_env()
 42 | 
 43 |     # Configure the root logger with proper format and level
 44 |     logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
 45 | 
 46 |     # Ensure the root logger for our package is also set correctly
 47 |     pkg_logger = logging.getLogger("mcp_server_tree_sitter")
 48 |     pkg_logger.setLevel(log_level)
 49 | 
 50 |     # Ensure all handlers have the correct level
 51 |     for handler in logging.root.handlers:
 52 |         handler.setLevel(log_level)
 53 | 
 54 |     # Ensure propagation is preserved
 55 |     pkg_logger.propagate = True
 56 | 
 57 |     # Ensure all existing loggers' handlers are synchronized
 58 |     for name in logging.root.manager.loggerDict:
 59 |         if name.startswith("mcp_server_tree_sitter"):
 60 |             logger = logging.getLogger(name)
 61 |             # Only synchronize handler levels, don't set logger level
 62 |             for handler in logger.handlers:
 63 |                 handler.setLevel(logger.getEffectiveLevel())
 64 | 
 65 | 
 66 | def update_log_levels(level_name: Union[str, int]) -> None:
 67 |     """
 68 |     Update the root package logger level and synchronize handler levels.
 69 | 
 70 |     This function sets the level of the root package logger only. Child loggers
 71 |     will inherit this level unless they have their own explicit level settings.
 72 |     Handler levels are updated to match their logger's effective level.
 73 | 
 74 |     Args:
 75 |         level_name: Log level name (DEBUG, INFO, etc.) or numeric value
 76 |     """
 77 |     # Convert string level name to numeric value if needed
 78 |     if isinstance(level_name, str):
 79 |         level_value = LOG_LEVEL_MAP.get(level_name.upper(), logging.INFO)
 80 |     else:
 81 |         level_value = level_name
 82 | 
 83 |     # Update ONLY the root package logger level
 84 |     pkg_logger = logging.getLogger("mcp_server_tree_sitter")
 85 |     pkg_logger.setLevel(level_value)
 86 | 
 87 |     # Update all handlers on the root package logger
 88 |     for handler in pkg_logger.handlers:
 89 |         handler.setLevel(level_value)
 90 | 
 91 |     # Also update the root logger for consistency - this helps with debug flag handling
 92 |     # when the module is already imported
 93 |     root_logger = logging.getLogger()
 94 |     root_logger.setLevel(level_value)
 95 |     for handler in root_logger.handlers:
 96 |         handler.setLevel(level_value)
 97 | 
 98 |     # Synchronize handler levels with their logger's effective level
 99 |     # for all existing loggers in our package hierarchy
100 |     for name in logging.root.manager.loggerDict:
101 |         if name == "mcp_server_tree_sitter" or name.startswith("mcp_server_tree_sitter."):
102 |             logger = logging.getLogger(name)
103 | 
104 |             # DO NOT set the logger's level explicitly to maintain hierarchy
105 |             # Only synchronize handler levels with the logger's effective level
106 |             for handler in logger.handlers:
107 |                 handler.setLevel(logger.getEffectiveLevel())
108 | 
109 |             # Ensure propagation is preserved
110 |             logger.propagate = True
111 | 
112 | 
113 | def get_logger(name: str) -> logging.Logger:
114 |     """
115 |     Get a properly configured logger with appropriate level.
116 | 
117 |     Args:
118 |         name: Logger name, typically __name__
119 | 
120 |     Returns:
121 |         logging.Logger: Configured logger
122 |     """
123 |     logger = logging.getLogger(name)
124 | 
125 |     # Only set level explicitly for the root package logger
126 |     # Child loggers will inherit levels as needed
127 |     if name == "mcp_server_tree_sitter":
128 |         log_level = get_log_level_from_env()
129 |         logger.setLevel(log_level)
130 | 
131 |         # Ensure all handlers have the correct level
132 |         for handler in logger.handlers:
133 |             handler.setLevel(log_level)
134 |     else:
135 |         # For child loggers, ensure handlers match their effective level
136 |         # without setting the logger level explicitly
137 |         effective_level = logger.getEffectiveLevel()
138 |         for handler in logger.handlers:
139 |             handler.setLevel(effective_level)
140 | 
141 |         # Ensure propagation is enabled
142 |         logger.propagate = True
143 | 
144 |     return logger
145 | 
146 | 
147 | # Run the root logger configuration when this module is imported
148 | configure_root_logger()
149 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/utils/context/mcp_context.py:
--------------------------------------------------------------------------------

```python
  1 | """Context handling for MCP operations with progress reporting."""
  2 | 
  3 | import logging
  4 | from contextlib import contextmanager
  5 | from typing import Any, Generator, Optional, TypeVar
  6 | 
  7 | logger = logging.getLogger(__name__)
  8 | 
  9 | T = TypeVar("T")
 10 | 
 11 | 
 12 | class ProgressScope:
 13 |     """Scope for tracking progress of an operation."""
 14 | 
 15 |     def __init__(self, context: "MCPContext", total: int, description: str):
 16 |         """
 17 |         Initialize a progress scope.
 18 | 
 19 |         Args:
 20 |             context: The parent MCPContext
 21 |             total: Total number of steps
 22 |             description: Description of the operation
 23 |         """
 24 |         self.context = context
 25 |         self.total = total
 26 |         self.description = description
 27 |         self.current = 0
 28 | 
 29 |     def update(self, step: int = 1) -> None:
 30 |         """
 31 |         Update progress by a number of steps.
 32 | 
 33 |         Args:
 34 |             step: Number of steps to add to progress
 35 |         """
 36 |         self.current += step
 37 |         if self.current > self.total:
 38 |             self.current = self.total
 39 |         self.context.report_progress(self.current, self.total)
 40 | 
 41 |     def set_progress(self, current: int) -> None:
 42 |         """
 43 |         Set progress to a specific value.
 44 | 
 45 |         Args:
 46 |             current: Current progress value
 47 |         """
 48 |         self.current = max(0, min(current, self.total))
 49 |         self.context.report_progress(self.current, self.total)
 50 | 
 51 | 
 52 | class MCPContext:
 53 |     """Context for MCP operations with progress reporting."""
 54 | 
 55 |     def __init__(self, ctx: Optional[Any] = None):
 56 |         """
 57 |         Initialize context with optional MCP context.
 58 | 
 59 |         Args:
 60 |             ctx: MCP context object, if available
 61 |         """
 62 |         self.ctx = ctx
 63 |         self.total_steps = 0
 64 |         self.current_step = 0
 65 | 
 66 |     def report_progress(self, current: int, total: int) -> None:
 67 |         """
 68 |         Report progress to the MCP client.
 69 | 
 70 |         Args:
 71 |             current: Current progress value
 72 |             total: Total steps
 73 |         """
 74 |         self.current_step = current
 75 |         self.total_steps = total
 76 | 
 77 |         if self.ctx and hasattr(self.ctx, "report_progress"):
 78 |             # Use MCP context if available
 79 |             try:
 80 |                 self.ctx.report_progress(current, total)
 81 |             except Exception as e:
 82 |                 logger.warning(f"Failed to report progress: {e}")
 83 |         else:
 84 |             # Log progress if no MCP context
 85 |             if total > 0:
 86 |                 percentage = int((current / total) * 100)
 87 |                 logger.debug(f"Progress: {percentage}% ({current}/{total})")
 88 | 
 89 |     def info(self, message: str) -> None:
 90 |         """
 91 |         Log an info message.
 92 | 
 93 |         Args:
 94 |             message: Message to log
 95 |         """
 96 |         logger.info(message)
 97 |         if self.ctx and hasattr(self.ctx, "info"):
 98 |             try:
 99 |                 self.ctx.info(message)
100 |             except Exception as e:
101 |                 logger.warning(f"Failed to send info message: {e}")
102 | 
103 |     def warning(self, message: str) -> None:
104 |         """
105 |         Log a warning message.
106 | 
107 |         Args:
108 |             message: Message to log
109 |         """
110 |         logger.warning(message)
111 |         if self.ctx and hasattr(self.ctx, "warning"):
112 |             try:
113 |                 self.ctx.warning(message)
114 |             except Exception as e:
115 |                 logger.warning(f"Failed to send warning message: {e}")
116 | 
117 |     def error(self, message: str) -> None:
118 |         """
119 |         Log an error message.
120 | 
121 |         Args:
122 |             message: Message to log
123 |         """
124 |         logger.error(message)
125 |         if self.ctx and hasattr(self.ctx, "error"):
126 |             try:
127 |                 self.ctx.error(message)
128 |             except Exception as e:
129 |                 logger.warning(f"Failed to send error message: {e}")
130 | 
131 |     @contextmanager
132 |     def progress_scope(self, total: int, description: str) -> Generator[ProgressScope, None, None]:
133 |         """
134 |         Context manager for tracking progress of an operation.
135 | 
136 |         Args:
137 |             total: Total number of steps
138 |             description: Description of the operation
139 | 
140 |         Yields:
141 |             ProgressScope object for updating progress
142 |         """
143 |         try:
144 |             self.info(f"Starting: {description}")
145 |             scope = ProgressScope(self, total, description)
146 |             scope.update(0)  # Set initial progress to 0
147 |             yield scope
148 |         finally:
149 |             if scope.current < scope.total:
150 |                 scope.set_progress(scope.total)  # Ensure we complete the progress
151 |             self.info(f"Completed: {description}")
152 | 
153 |     def with_mcp_context(self, ctx: Any) -> "MCPContext":
154 |         """
155 |         Create a new context with the given MCP context.
156 | 
157 |         Args:
158 |             ctx: MCP context object
159 | 
160 |         Returns:
161 |             New MCPContext with the given MCP context
162 |         """
163 |         return MCPContext(ctx)
164 | 
165 |     @staticmethod
166 |     def from_mcp_context(ctx: Optional[Any]) -> "MCPContext":
167 |         """
168 |         Create a context from an MCP context.
169 | 
170 |         Args:
171 |             ctx: MCP context object or None
172 | 
173 |         Returns:
174 |             New MCPContext
175 |         """
176 |         return MCPContext(ctx)
177 | 
178 |     def try_get_mcp_context(self) -> Optional[Any]:
179 |         """
180 |         Get the wrapped MCP context if available.
181 | 
182 |         Returns:
183 |             MCP context or None
184 |         """
185 |         return self.ctx
186 | 
```

--------------------------------------------------------------------------------
/src/mcp_server_tree_sitter/capabilities/server_capabilities.py:
--------------------------------------------------------------------------------

```python
  1 | """Server capability declarations for MCP integration."""
  2 | 
  3 | import logging
  4 | from typing import Any, Dict, List
  5 | 
  6 | logger = logging.getLogger(__name__)
  7 | 
  8 | 
  9 | def register_capabilities(mcp_server: Any) -> None:
 10 |     """
 11 |     Register MCP server capabilities.
 12 | 
 13 |     Args:
 14 |         mcp_server: MCP server instance
 15 |     """
 16 |     # Use dependency injection instead of global context
 17 |     from ..di import get_container
 18 | 
 19 |     # Get container and dependencies
 20 |     container = get_container()
 21 |     config_manager = container.config_manager
 22 |     config = config_manager.get_config()
 23 | 
 24 |     # FastMCP may not have capability method, so we'll skip this for now
 25 |     # @mcp_server.capability("prompts.listChanged")
 26 |     def handle_prompts_list_changed() -> Dict[str, Any]:
 27 |         """Handle prompt template management events."""
 28 |         logger.debug("Received prompts.listChanged event")
 29 |         return {"status": "success"}
 30 | 
 31 |     # @mcp_server.capability("resources.subscribe")
 32 |     def handle_resources_subscribe(resource_uri: str) -> Dict[str, Any]:
 33 |         """
 34 |         Handle resource subscription requests.
 35 | 
 36 |         Args:
 37 |             resource_uri: Resource URI to subscribe to
 38 | 
 39 |         Returns:
 40 |             Subscription response
 41 |         """
 42 |         logger.debug(f"Received subscription request for {resource_uri}")
 43 |         return {"status": "success", "resource": resource_uri}
 44 | 
 45 |     # @mcp_server.capability("resources.listChanged")
 46 |     def handle_resources_list_changed() -> Dict[str, Any]:
 47 |         """Handle resource discovery events."""
 48 |         logger.debug("Received resources.listChanged event")
 49 |         return {"status": "success"}
 50 | 
 51 |     # @mcp_server.capability("tools.listChanged")
 52 |     def handle_tools_list_changed() -> Dict[str, Any]:
 53 |         """Handle tool discovery events."""
 54 |         logger.debug("Received tools.listChanged event")
 55 |         return {"status": "success"}
 56 | 
 57 |     # @mcp_server.capability("logging")
 58 |     def handle_logging(level: str, message: str) -> Dict[str, Any]:
 59 |         """
 60 |         Handle logging configuration.
 61 | 
 62 |         Args:
 63 |             level: Log level
 64 |             message: Log message
 65 | 
 66 |         Returns:
 67 |             Logging response
 68 |         """
 69 |         log_levels = {
 70 |             "debug": logging.DEBUG,
 71 |             "info": logging.INFO,
 72 |             "warning": logging.WARNING,
 73 |             "error": logging.ERROR,
 74 |         }
 75 | 
 76 |         log_level = log_levels.get(level.lower(), logging.INFO)
 77 |         logger.log(log_level, f"MCP: {message}")
 78 | 
 79 |         return {"status": "success"}
 80 | 
 81 |     # @mcp_server.capability("completion")
 82 |     def handle_completion(text: str, position: int) -> Dict[str, Any]:
 83 |         """
 84 |         Handle argument completion suggestions.
 85 | 
 86 |         Args:
 87 |             text: Current input text
 88 |             position: Cursor position in text
 89 | 
 90 |         Returns:
 91 |             Completion suggestions
 92 |         """
 93 |         # Simple completion for commonly used arguments
 94 |         suggestions: List[Dict[str, str]] = []
 95 | 
 96 |         # Extract the current word being typed
 97 |         current_word = ""
 98 |         i = position - 1
 99 |         while i >= 0 and text[i].isalnum() or text[i] == "_":
100 |             current_word = text[i] + current_word
101 |             i -= 1
102 | 
103 |         # Project name suggestions
104 |         if current_word and "project" in text[:position].lower():
105 |             # Use container's project registry
106 |             project_registry = container.project_registry
107 |             for project_dict in project_registry.list_projects():
108 |                 project_name = project_dict["name"]
109 |                 if project_name.startswith(current_word):
110 |                     suggestions.append(
111 |                         {
112 |                             "text": project_name,
113 |                             "description": f"Project: {project_name}",
114 |                         }
115 |                     )
116 | 
117 |         # Language suggestions
118 |         if current_word and "language" in text[:position].lower():
119 |             # Use container's language registry
120 |             language_registry = container.language_registry
121 |             for language in language_registry.list_available_languages():
122 |                 if language.startswith(current_word):
123 |                     suggestions.append({"text": language, "description": f"Language: {language}"})
124 | 
125 |         # Config suggestions
126 |         if current_word and "config" in text[:position].lower():
127 |             if "cache_enabled".startswith(current_word):
128 |                 suggestions.append(
129 |                     {
130 |                         "text": "cache_enabled",
131 |                         "description": f"Cache enabled: {config.cache.enabled}",
132 |                     }
133 |                 )
134 |             if "max_file_size_mb".startswith(current_word):
135 |                 # Store in variable to avoid line length error
136 |                 size_mb = config.security.max_file_size_mb
137 |                 suggestions.append(
138 |                     {
139 |                         "text": "max_file_size_mb",
140 |                         "description": f"Max file size: {size_mb} MB",
141 |                     }
142 |                 )
143 |             if "log_level".startswith(current_word):
144 |                 suggestions.append(
145 |                     {
146 |                         "text": "log_level",
147 |                         "description": f"Log level: {config.log_level}",
148 |                     }
149 |                 )
150 | 
151 |         return {"suggestions": suggestions}
152 | 
153 |     # Ensure capabilities are accessible to tests
154 |     if hasattr(mcp_server, "capabilities"):
155 |         mcp_server.capabilities["logging"] = handle_logging
156 |         mcp_server.capabilities["completion"] = handle_completion
157 | 
```

--------------------------------------------------------------------------------
/docs/architecture.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Architecture Overview
  2 | 
  3 | This document provides an overview of the MCP Tree-sitter Server's architecture, focusing on key components and design patterns.
  4 | 
  5 | ## Core Architecture
  6 | 
  7 | The MCP Tree-sitter Server follows a structured architecture with the following components:
  8 | 
  9 | 1. **Bootstrap Layer**: Core initialization systems that must be available to all modules with minimal dependencies
 10 | 2. **Configuration Layer**: Configuration management with environment variable support
 11 | 3. **Dependency Injection Container**: Central container for managing and accessing services
 12 | 4. **Tree-sitter Integration**: Interfaces with the tree-sitter library for parsing and analysis
 13 | 5. **MCP Protocol Layer**: Handles interactions with the Model Context Protocol
 14 | 
 15 | ## Bootstrap Layer
 16 | 
 17 | The bootstrap layer handles critical initialization tasks that must happen before anything else:
 18 | 
 19 | ```
 20 | src/mcp_server_tree_sitter/bootstrap/
 21 | ├── __init__.py           # Exports key bootstrap functions
 22 | └── logging_bootstrap.py  # Canonical logging configuration
 23 | ```
 24 | 
 25 | This layer is imported first in the package's `__init__.py` and has minimal dependencies. The bootstrap module ensures that core services like logging are properly initialized and globally available to all modules.
 26 | 
 27 | **Key Design Principle**: Each component in the bootstrap layer must have minimal dependencies to avoid import cycles and ensure reliable initialization.
 28 | 
 29 | ## Dependency Injection Pattern
 30 | 
 31 | Instead of using global variables (which was the approach in earlier versions), the application now uses a structured dependency injection pattern:
 32 | 
 33 | 1. **DependencyContainer**: The `DependencyContainer` class holds all application components and services
 34 | 2. **ServerContext**: A context class provides a clean interface for interacting with dependencies
 35 | 3. **Access Functions**: API functions like `get_logger()` and `update_log_levels()` provide easy access to functionality
 36 | 
 37 | This approach has several benefits:
 38 | - Cleaner testing with the ability to mock dependencies
 39 | - Better encapsulation of implementation details
 40 | - Reduced global state and improved thread safety
 41 | - Clearer dependency relationships between components
 42 | 
 43 | ## Logging Design
 44 | 
 45 | Logging follows a hierarchical model using Python's standard `logging` module:
 46 | 
 47 | 1. **Root Package Logger**: Only the root package logger (`mcp_server_tree_sitter`) has its level explicitly set
 48 | 2. **Child Loggers**: Child loggers inherit their level from the root package logger
 49 | 3. **Handler Synchronization**: Handler levels are synchronized with their logger's effective level
 50 | 
 51 | **Canonical Implementation**: The logging system is defined in a single location - `bootstrap/logging_bootstrap.py`. Other modules import from this module to ensure consistent behavior.
 52 | 
 53 | ### Logging Functions
 54 | 
 55 | The bootstrap module provides these key logging functions:
 56 | 
 57 | ```python
 58 | # Get log level from environment variable
 59 | get_log_level_from_env()
 60 | 
 61 | # Configure the root logger
 62 | configure_root_logger()
 63 | 
 64 | # Get a properly configured logger
 65 | get_logger(name)
 66 | 
 67 | # Update log levels
 68 | update_log_levels(level_name)
 69 | ```
 70 | 
 71 | ## Configuration System
 72 | 
 73 | The configuration system uses a layered approach:
 74 | 
 75 | 1. **Environment Variables**: Highest precedence (e.g., `MCP_TS_LOG_LEVEL=DEBUG`)
 76 | 2. **Explicit Updates**: Updates made via `update_value()` calls
 77 | 3. **YAML Configuration**: Settings from YAML configuration files
 78 | 4. **Default Values**: Fallback defaults defined in model classes
 79 | 
 80 | The `ConfigurationManager` is responsible for loading, managing, and applying configuration, while a `ServerConfig` model encapsulates the actual configuration settings.
 81 | 
 82 | ## Project and Language Management
 83 | 
 84 | Projects and languages are managed by registry classes:
 85 | 
 86 | 1. **ProjectRegistry**: Maintains active project registrations
 87 | 2. **LanguageRegistry**: Manages tree-sitter language parsers
 88 | 
 89 | These registries are accessed through the dependency container or context, providing a clean interface for operations.
 90 | 
 91 | ## Use of Builder and Factory Patterns
 92 | 
 93 | The server uses several design patterns for cleaner code:
 94 | 
 95 | 1. **Builder Pattern**: Used for constructing complex objects like `Project` instances
 96 | 2. **Factory Methods**: Used to create tree-sitter parsers and queries
 97 | 3. **Singleton Pattern**: Used for the dependency container to ensure consistent state
 98 | 
 99 | ## Lifecycle Management
100 | 
101 | The server's lifecycle is managed in a structured way:
102 | 
103 | 1. **Bootstrap Phase**: Initializes logging and critical systems (from `__init__.py`)
104 | 2. **Configuration Phase**: Loads configuration from files and environment
105 | 3. **Dependency Initialization**: Sets up all dependencies in the container
106 | 4. **Server Setup**: Configures MCP tools and capabilities
107 | 5. **Running Phase**: Processes requests from the MCP client
108 | 6. **Shutdown**: Gracefully handles shutdown and cleanup
109 | 
110 | ## Error Handling Strategy
111 | 
112 | The server implements a layered error handling approach:
113 | 
114 | 1. **Custom Exceptions**: Defined in `exceptions.py` for specific error cases
115 | 2. **Function-Level Handlers**: Most low-level functions do error handling
116 | 3. **Tool-Level Handlers**: MCP tools handle errors and return structured responses
117 | 4. **Global Exception Handling**: FastMCP provides top-level error handling
118 | 
119 | ## Future Architecture Improvements
120 | 
121 | Planned architectural improvements include:
122 | 
123 | 1. **Complete Decoupling**: Further reduce dependencies between components
124 | 2. **Module Structure Refinement**: Better organize modules by responsibility
125 | 3. **Configuration Caching**: Optimize configuration access patterns
126 | 4. **Async Support**: Add support for asynchronous operations
127 | 5. **Plugin Architecture**: Support for extensibility through plugins
128 | 
```
Page 1/5FirstPrevNextLast