# How DeusData/codebase-memory-mcp Implements Hybrid LSP Semantic Type Resolution

> Discover how DeusData/codebase-memory-mcp implements Hybrid LSP semantic type resolution with a lightweight C engine. Analyze cross-file imports and call graphs efficiently for nine language families.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: deep-dive
- Published: 2026-07-16

---

**The `codebase-memory-mcp` project implements Hybrid LSP semantic type resolution as a lightweight C-based engine that mimics modern language servers (tsserver, pyright, rust-analyzer) without requiring external processes, enabling static analysis of cross-file imports and call graphs across nine language families.**

The `codebase-memory-mcp` repository builds a persistent knowledge graph of codebases by combining tree-sitter parsing with a second pass called **Hybrid LSP**. This pass runs a native C implementation of type-resolution algorithms immediately after AST generation, producing IDE-grade semantic information that gets stored directly in an SQLite-backed graph.

## The Six-Phase Hybrid LSP Pipeline

Each language implementation in `internal/cbm/lsp/` follows a standardized six-phase workflow. While specific syntax varies by language, the core pattern remains consistent across all nine supported families.

### Phase 1: Import Binding

The `py_lsp_bind_imports` function in [`internal/cbm/lsp/py_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/py_lsp.c) collects all import statements and populates a scoped name-to-type map. This handles both `import X` and `from X import Y` patterns in Python, creating the initial symbol table for resolution.

### Phase 2: Statement Processing

Top-level statements—including assignments, `for` loops, `with` blocks, and class or function definitions—are walked to register variables, fields, and methods in the current scope. In Python, this is implemented by `py_process_statement`.

### Phase 3: Expression Type Evaluation

Individual expressions are evaluated to determine their static types. The `py_eval_expr_type` function handles literals, binary operations, and function calls at the expression level.

### Phase 4: Attribute Lookup

Attribute chains (e.g., `obj.attr.method`) are resolved using language-specific rules like Python's Method Resolution Order (MRO), falling back to the type registry for built-ins. This is implemented by `py_lookup_attribute`.

### Phase 5: Call Resolution

The system recursively walks the AST, emitting `CBMResolvedCall` structures for every callable expression. The `py_resolve_calls_in` function produces fully-qualified callee names with confidence scores, representing the final resolved call edges.

### Phase 6: Cross-File Linking

After processing all files, the import maps are used to resolve re-exports, wildcard imports, and intra-package references (Phase 9 in the source), completing the cross-file dependency graph.

## Core Data Structures

The Hybrid LSP implementation relies on three shared abstractions defined in [`internal/cbm/lsp/type_registry.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/type_registry.c) and related headers:

- **CBMScope**: A hierarchical symbol table managed by `cbm_scope_*` functions that maintains name-to-type bindings across nested scopes.
- **CBMTypeRegistry**: Pre-computed tables of standard-library types for each language, stored in generated files under [`internal/cbm/lsp/generated/..._stdlib_data.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/generated/..._stdlib_data.c).
- **CBMResolvedCallArray**: A dynamic array of resolved call edges that ultimately become `RESOLVED_CALLS` relationships in the knowledge graph.

## Language-Specific Implementations

Each supported language maintains its own implementation file in `internal/cbm/lsp/`, following the six-phase pattern but adapting to language-specific semantics:

- **[`py_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/py_lsp.c)**: Python resolution with MRO support and import handling
- **[`ts_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/ts_lsp.c)**: TypeScript/JavaScript with generic substitution and JSX component dispatch
- **[`go_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/go_lsp.c)**: Go package imports and method set resolution
- **[`rust_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/rust_lsp.c)**: Rust trait method resolution and UFCS (Universal Function Call Syntax)

All implementations generate data compatible with the shared `CBMResolvedCall` format, ensuring consistent graph representation across languages.

## Practical Implementation Examples

### Indexing a Single Python File

The following C code demonstrates initializing the Hybrid LSP context for Python and resolving calls:

```c
/* Allocate an arena for temporary allocations */
CBMArena *arena = cbm_arena_new();

/* Load source text (normally read from a file) */
const char *src = "... Python source ...";
int src_len = strlen(src);

/* Registry contains stdlib types for Python (generated data) */
extern const CBMTypeRegistry python_registry;
CBMResolvedCallArray calls = {0};

/* Initialise the LSP context */
PyLSPContext ctx;
py_lsp_init(&ctx, arena, src, src_len,
            &python_registry,
            "__main__", &calls);

/* Register imports that were previously extracted by the tree‑sitter pass */
py_lsp_add_import(&ctx, "os", "os");
py_lsp_add_import(&ctx, "np", "numpy");

/* Bind imports into the scope */
py_lsp_bind_imports(&ctx);

/* Walk the AST and emit resolved calls */
TSNode root = ts_tree_root_node(ctx.tree);
py_resolve_calls_in(&ctx, root);

/* `calls` now contains fully‑qualified callee names and confidence scores */
for (size_t i = 0; i < calls.len; ++i) {
    printf("call %zu: %s (conf %.2f)\n",
           i, calls.items[i].callee_qn, calls.items[i].confidence);
}

```

### Querying the Persisted Graph

Once indexed, resolved calls are stored as `RESOLVED_CALLS` edges in the SQLite graph:

```bash

# After indexing the whole repository:

codebase-memory-mcp query_graph '
MATCH (f:Function)-[:RESOLVED_CALLS]->(g:Function)
WHERE f.file = "src/main.py"
RETURN f.name, g.name, g.module
' | jq .

```

This returns the same call edges emitted during the Hybrid LSP pass, now queryable without a live language server.

### Adding TypeScript Support

New languages follow the same structural pattern. The TypeScript entry points in [`internal/cbm/lsp/ts_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/ts_lsp.c) mirror the Python API:

```c
void ts_lsp_init(TsLSPContext *ctx, …);
void ts_lsp_add_import(TsLSPContext *ctx, const char *local, const char *module_qn);
void ts_lsp_bind_imports(TsLSPContext *ctx);
void ts_resolve_calls_in(TsLSPContext *ctx, TSNode node);

```

This consistency ensures that extending support to additional languages requires only implementing language-specific rules (e.g., generic substitution for TypeScript, trait dispatch for Rust) while reusing the shared infrastructure.

## Summary

- **Hybrid LSP semantic type resolution** in `codebase-memory-mcp` combines tree-sitter ASTs with a native C type engine to resolve symbols without external LSP processes.
- The implementation follows a **six-phase pipeline**: import binding, statement processing, expression evaluation, attribute lookup, call resolution, and cross-file linking.
- **Core data structures** (`CBMScope`, `CBMTypeRegistry`, `CBMResolvedCallArray`) are shared across nine language implementations located in `internal/cbm/lsp/`.
- Resolved calls are persisted as **graph edges** (`RESOLVED_CALLS`), enabling fast structural queries for AI coding agents.
- The system supports **Python, TypeScript/JavaScript/JSX/TSX, PHP, C#, Go, C/C++, Java, Kotlin, and Rust** with language-specific semantics handled in dedicated source files.

## Frequently Asked Questions

### What is the difference between Hybrid LSP and a standard language server?

Standard language servers like pyright or tsserver run as persistent processes that analyze code on demand. Hybrid LSP semantic type resolution implements similar algorithms in a lightweight C library that runs once during indexing, producing static data stored in SQLite. This eliminates runtime dependencies on external LSP processes while maintaining IDE-grade accuracy for import resolution and type inference.

### How does the system handle complex Python imports like `from package import *`?

The import binding phase in `py_lsp_bind_imports` handles both explicit imports and wildcard patterns. During the cross-file linking phase (Phase 6), the system resolves wildcard imports and re-exports by consulting the pre-computed import maps from all indexed files, ensuring that symbols imported via wildcard are correctly linked to their origins in the knowledge graph.

### Can the Hybrid LSP resolve generic types in TypeScript?

Yes. The TypeScript implementation in [`internal/cbm/lsp/ts_lsp.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/internal/cbm/lsp/ts_lsp.c) includes specific logic for generic substitution and JSX component dispatch. When resolving expression types, the system substitutes concrete types for generic parameters, allowing accurate resolution of calls like `Array<string>.map` by tracking the type arguments through the scope chain.

### Where are the standard library type definitions stored?

Standard library types for each language are stored in auto-generated C files under `internal/cbm/lsp/generated/`. For example, [`generated/python_stdlib_data.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/generated/python_stdlib_data.c) contains pre-computed tables of Python built-in types. These are loaded into `CBMTypeRegistry` structures at runtime to provide fallback type information during attribute lookup and call resolution.