Hybrid LSP Implementation in codebase-memory-mcp: Multi-Language Type Resolution

The hybrid LSP implementation in codebase-memory-mcp embeds lightweight C-based type resolvers that run directly on tree-sitter ASTs, eliminating external LSP process overhead while enabling precise CALLS, USAGE, and RESOLVED_CALLS edges across Python, TypeScript, Go, Rust, and seven other languages.

The codebase-memory-mcp repository (DeusData/codebase-memory-mcp) introduces a novel hybrid LSP implementation that bridges the gap between syntactic parsing and full language-server intelligence. Unlike traditional approaches that spawn external processes like pyright or rust-analyzer, this system embeds native C implementations that replicate core type-resolution logic while operating directly on existing tree-sitter abstract syntax trees. This architecture enables the graph builder to create type-aware edges without the latency and resource overhead of inter-process communication.

What Is the Hybrid LSP Layer?

The Hybrid LSP layer serves as the second phase of the indexing pipeline, running immediately after the initial tree-sitter syntactic pass (as described in the README at lines 662-689). Rather than implementing full Language Server Protocol specifications, each module provides targeted type-resolution logic that mirrors the behavior of mainstream language servers.

The system resides in internal/cbm/lsp/ and consists of individual C implementations for each supported language. A central dispatcher in internal/cbm/lsp_all.c routes files to their appropriate resolver, while src/pipeline/pass_lsp_cross.c integrates the results into the cross-file registry and refines raw call edges with type information.

Supported Languages and Implementation Files

Each language implementation resides in a dedicated C file under internal/cbm/lsp/. These files reproduce the specific type-resolution behaviors of their respective mainstream language servers.

Python

File: internal/cbm/lsp/py_lsp.c

The Python hybrid LSP resolves imports and dotted sub-module walks, dataclasses, Self return types, and generics. It handles @property decorators, pattern-matching class patterns, and framework-specific types including SQLAlchemy 2.0 Mapped[T] and Pydantic models. Additional capabilities include typing.Annotated resolution, type narrowing via isinstance and is not None checks, walrus operator assignments, and typing.cast or assert_type assertions. The implementation also manages async/await patterns, class/static method dispatch, and stdlib modules such as logging, pathlib, json, and functools.

TypeScript and JavaScript

File: internal/cbm/lsp/ts_lsp.c

This implementation covers TypeScript, JavaScript, JSX, and TSX files. It resolves generics, JSX component dispatch, and JSDoc inference for plain JavaScript. The LSP handles .d.ts declarations, re-exports, and method-chaining via return-type propagation, maintaining a cross-file registry for module resolution.

Go

File: internal/cbm/lsp/go_lsp.c

The Go implementation provides per-package cross-file registry management, generic type resolution, and embedded struct analysis. It determines interface satisfaction and handles package-aware import resolution without requiring the Go toolchain.

Rust

File: internal/cbm/lsp/rust_lsp.c

Rust support covers use declarations, module paths, and impl blocks including trait methods. The resolver handles struct fields, generics with trait bounds, operator-trait desugaring, and derive-macro method synthesis. It implements Universal Function Call Syntax (UFCS) for static path resolution and recognizes common stdlib prelude items.

Java

File: internal/cbm/lsp/java_lsp.c

The Java LSP resolves single-type and on-demand imports, including static imports. It navigates class hierarchies with this and super dispatch, handles generics, annotations, and overload matching. The implementation also supports lambdas, method references, and field-type inference for common JDK stdlib classes.

C#

File: internal/cbm/lsp/cs_lsp.c

For C#, the resolver manages global usings and file-scoped namespaces. It handles records including C# 12 primary constructors, LINQ method syntax, and async Task<T> or ValueTask<T> unwrapping. The implementation covers generic methods, this/base dispatch, var inference, and common Base Class Library (BCL) types.

C and C++

File: internal/cbm/lsp/c_lsp.c

The C/C++ implementation distinguishes between the two languages. For C, it resolves macro expansion, typedef chains, and header-source linking. For C++, it handles templates, namespaces, auto inference, and method resolution via class hierarchies.

Kotlin

File: internal/cbm/lsp/kotlin_lsp.c

Kotlin support includes imports, classes/objects, and companion objects. The LSP resolves extension functions, data classes, nullable-type unwrapping, and scope functions (let, apply, run, also, with). It also handles infix calls and stdlib resolution.

PHP

File: internal/cbm/lsp/php_lsp.c

The PHP implementation resolves namespaces, traits, and late-static-binding. It performs PHPDoc inference, parameter binding, and return-type inference across the codebase.

Perl

File: internal/cbm/lsp/perl_lsp.c

Perl support covers packages, @ISA/use parent/use base inheritance, and SUPER:: calls. The resolver handles Exporter import maps, bless/ref self-type inference, and qualified Pkg::sub static calls for curated perlfunc and CPAN OOP stdlib usage.

Architecture and Pipeline Integration

The hybrid LSP pass operates as a refinement layer within the indexing pipeline. After the tree-sitter syntactic pass constructs the initial graph, the system invokes internal/cbm/lsp_all.c to dispatch each source file to its language-specific resolver.

The src/pipeline/pass_lsp_cross.c file coordinates cross-file analysis, building a registry of type definitions and method signatures that enable resolution of calls across module boundaries. This produces type-aware edges such as RESOLVED_CALLS that distinguish between syntactically similar but semantically distinct function invocations.

Languages without a dedicated implementation fall back to the syntactic resolver, ensuring the pipeline always produces results, albeit with reduced precision for type-dependent queries.

Querying Type-Resolved Graph Edges

Once the repository has been indexed, the CLI exposes the type-resolved relationships through specific graph queries. The following commands demonstrate how to leverage the hybrid LSP results:


# Find the qualified name of a function resolved via LSP

codebase-memory-mcp cli search_graph \
  '{"project":"my-project","label":"Function","name_pattern":"^User\\.profile\\.display_name$"}'

# Retrieve the call-graph edge that was type-resolved

codebase-memory-mcp cli trace_path \
  '{"project":"my-project","function_name":"User.profile.display_name","direction":"inbound"}'

# Verify the edge originates from a different module (type-aware resolution)

codebase-memory-mcp cli get_code_snippet \
  '{"project":"my-project","qualified_name":"Service.account.get_user"}'

In the Python example above, a call to user.profile.display_name() resolves to the specific Profile.display_name method implementation rather than any syntactically matching function, thanks to the type information provided by py_lsp.c.

Summary

  • Embedded C implementations in internal/cbm/lsp/ provide language-specific type resolution without external LSP processes.
  • Ten languages are supported: Python, TypeScript/JavaScript, Go, Rust, Java, C#, C/C++, Kotlin, PHP, and Perl.
  • Type-aware edges including CALLS, USAGE, and RESOLVED_CALLS are generated by refining tree-sitter ASTs through the hybrid LSP layer.
  • Pipeline integration occurs via src/pipeline/pass_lsp_cross.c and the dispatcher in internal/cbm/lsp_all.c.
  • Fallback behavior ensures syntactic resolution remains available for languages without dedicated LSP implementations.

Frequently Asked Questions

How does the hybrid LSP differ from a traditional language server?

Traditional language servers like pyright or rust-analyzer run as separate processes and communicate via the Language Server Protocol, requiring JSON-RPC overhead and significant memory footprint. The hybrid LSP implementation in codebase-memory-mcp embeds lightweight C resolvers that operate directly on tree-sitter ASTs within the same process, eliminating IPC latency while providing the specific type information needed for call-graph construction.

Which languages support full type resolution in the hybrid LSP?

According to the repository README (lines 662-682), full hybrid LSP support is available for Python, TypeScript/JavaScript (including JSX/TSX), Go, Rust, Java, C#, C/C++, Kotlin, PHP, and Perl. Each language has a dedicated implementation file in internal/cbm/lsp/ that replicates the core type-resolution logic of its mainstream language server.

What happens when a language lacks a hybrid LSP implementation?

When indexing a language without a dedicated LSP module, the system falls back to the syntactic resolver based purely on tree-sitter parsing. This guarantees that indexing succeeds and produces results, but call edges will not be type-resolved. For example, a method call might link to multiple candidates rather than the specific overriding implementation determined by type hierarchy.

How does the Python LSP handle framework-specific types like SQLAlchemy?

The Python hybrid LSP in internal/cbm/lsp/py_lsp.c includes specific logic for popular frameworks. It recognizes SQLAlchemy 2.0 Mapped[T] types, Pydantic model definitions, and typing.Annotated wrappers. This allows the graph builder to resolve relationships between ORM models and their actual column types, producing accurate USAGE edges for database-dependent codebases.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →