Skip to main content
Glama

Cartograph

에이전트 네이티브 코드 인텔리전스. 모든 저장소를 쿼리 가능한 코드 그래프로 바꾸고 MCP를 통해 코딩 에이전트에 제공하세요. 그러면 에이전트는 grep을 해대며 추측하는 대신 “이걸 바꾸면 뭐가 깨질까?” 를 물을 수 있습니다.

tree-sitter + SQLite. 임베딩도, 벡터 저장소도, API 키도, 서버도, 비용도 없습니다.

→ 라이브 데모 — 이 저장소의 실제 인덱스에서 푸시 때마다 생성됩니다.

CI Python 3.11+ License MIT


문제

코딩 에이전트에게 크고 익숙하지 않은 저장소를 주면 어떤 일을 하는지 지켜보세요: grep을 하고, 파일을 읽고, 또 grep을 하고, 다른 파일을 읽습니다. 파서가 한 번에 알려줄 수 있는 구조를 재구성하는 데 컨텍스트를 소진합니다. 그리고 변경으로 인해 망가뜨린 세 모듈 떨어진 호출자를 여전히 놓칩니다.

일반적인 해결책은 RAG입니다: 코드베이스를 임베딩하고 “비슷한” 청크를 검색합니다. 하지만 “이 함수를 누가 호출하나요?” 는 유사성 질문이 아닙니다. 정확한 답이 있고, 그 답은 호출 그래프에 있습니다.

Cartograph는 그래프를 구축한 다음 에이전트가 실제로 작업하는 방식에 맞춰진 열 가지 도구를 제공합니다.

$ cartograph blast src/cartograph/graph/store.py

## Blast radius — file `src/cartograph/graph/store.py`

17 dependent file(s), 31 affected symbol(s), 7 test file(s).

**Tests to run first**
- `tests/test_cli.py`
- `tests/test_docs.py`
- `tests/test_incremental.py`
- `tests/test_mcp.py`
- `tests/test_resolver.py`
- `tests/test_traversal.py`
- `tests/test_views.py`

**Dependent files** (by import distance)
- `src/cartograph/graph/resolver.py` · d1
- `src/cartograph/indexer/pipeline.py` · d1
- `src/cartograph/service.py` · d1
- `src/cartograph/cli.py` · d2
…

편집 전에 단 한 번의 호출. 테스트 스위트가 빨간불이 켜진 뒤의 일곱 번의 grep이 아닙니다.


빠른 시작

uv tool install cartograph-mcp     # or: pipx install cartograph-mcp

cartograph index ~/code/my-repo    # builds .cartograph/cartograph.db
cartograph arch                    # modules, layers, cycles, hotspots
cartograph blast src/auth/token.py # what a change here could break
cartograph callers validate_token  # reverse call tree

에이전트에 연결하기

Claude Code:

claude mcp add cartograph -- cartograph serve /path/to/repo

또는 mcp.json을 통해 모든 MCP 클라이언트:

{
  "mcpServers": {
    "cartograph": {
      "command": "cartograph",
      "args": ["serve", "/path/to/repo"]
    }
  }
}

serve는 인덱스가 없으면 첫 실행 시 인덱싱합니다. 그런 다음 에이전트에게 “토큰 검증기를 바꾸면 뭐가 깨질까?” 라고 물어보세요. 그러면 추측하는 대신 blast_radius를 호출합니다.


열 가지 도구

도구

답변

find_symbol

X가 정의된 위치는? (구조적 중요도순)

search_code

이름, 시그니처, 독스트링에 대한 전문 검색 (BM25)

get_symbol

하나의 심볼: 시그니처, 문서, 멤버, 호출자, 피호출자, 소스

who_calls

역방향 호출 트리 — 시그니처를 변경하기 전에

what_it_calls

정방향 호출 트리 — 모든 파일을 읽지 않고 코드 이해

blast_radius

변경으로 깨질 수 있는 것, 그리고 실행해야 할 테스트

related_symbols

개인화된 PageRank를 통한 “또 무엇을 읽어야 하나?”

file_summary

파일이 정의하고, 가져오고, 누가 가져오는지

architecture_overview

모듈, 계층화, 임포트 사이클, 핫스팟, 진입점

index_stats

인덱스 건강 상태와 규칙별 엣지 해석 내역

또한 MCP 리소스(cartograph://architecture, cartograph://stats)와 익숙하지 않은 저장소에 대한 그래프 우선 첫 탐색을 위한 orient 프롬프트가 있습니다.

지원 언어: Python, TypeScript, TSX, JavaScript, Go.


논쟁할 가치가 있는 설계 결정

1. 신뢰도는 일급 컬럼입니다

타입 체커 없이는 store.who_calls()GraphStore.who_calls를 의미한다고 수 없습니다. 가설의 순위를 매길 수만 있을 뿐입니다. 그래서인 척하지 않고, 모든 엣지는 그 엣지를 만들어낸 규칙과 신뢰도를 기록합니다:

규칙

신뢰도

직관

same-file

0.95

정의가 스코프 안에 바로 있다

import

0.90

파일이 이 이름을 명시적으로 임포트했다

receiver-type

0.85

Foo가 알려진 컨테이너인 Foo.bar()

same-module

0.75

같은 패키지의 형제 파일

unique-global

0.60

정확히 하나의 저장소 심볼만 이 이름을 가지며, 수식 없는 호출

name-only

0.45

하나의 일치하지만 타입이 없는 리시버에 대한 호출

ambiguous

≤0.40

N개의 후보, 각각 1/N 신뢰도의 N개 엣지로 유지

external

0.00

서드파티/표준 라이브러리 임포트에 뿌리를 둠

unresolved

0.00

진짜로 알 수 없음 (동적이거나 타입이 있는 메서드)

그러면 호출자(caller)는 자신의 운영 기준점을 선택합니다. who_calls는 기본적으로 ≥0.5입니다 — 정밀도 우선, 에이전트가 답을 바탕으로 행동하기 때문입니다. blast_radius는 0.3으로 낮춥니다 — 재현율 우선, 영향을 받는 테스트를 놓치는 것이 값비싼 실수이고 오탐(false positive)은 리뷰어가 한 번 훑어보는 비용만 들기 때문입니다.

name-only 계층은 실제 버그 때문에 존재합니다. 내장 setseen.add(...)가 이름이 우연히 유일하다는 이유만으로 저장소 클래스의 add 메서드로 해석되었고, 그것이 확신도 높은 호출자로 나타났습니다. 타입을 알 수 없는 리시버의 메서드 이름은 증거가 아니므로, 이제 정밀도 기준선 아래에 위치합니다. (test)

external은 메트릭에 대한 정직함을 위해 존재합니다. 대부분의 저장소에서 “unresolved” 버킷은 typer.Optionsqlite3.execute가 지배합니다. 그것들을 한데 묶으면 적용 범위가 실제보다 훨씬 나빠 보이므로, Cartograph는 내부 해석(internal resolution) 을 보고합니다 — 저장소 심볼에 도달할 수 있는 호출 사이트 중 실제로 도달한 비율입니다.

2. 파싱은 증분적이고 해석은 결코 증분적이지 않다

파일은 sha256이 변경될 때만 다시 파싱됩니다. 그러나 원시 참조는 refs 테이블에 사실(facts) 로 저장되고, edges는 무언가 변경될 때마다 (refs × symbols)의 순수 함수로 다시 계산됩니다.

이것이 “편집할 때마다 다시 인덱싱”을 신뢰할 수 있게 만듭니다. 해석도 증분식이라면 한 파일을 편집할 때 다른 파일의 엣지가 이동한 심볼을 가리키는 채로 남을 수 있습니다. 전역 재해석은 이를 구조적으로 불가능하게 만듭니다. (test)

비용은 실재하므로 안전한 지름길은 정확히 하나뿐입니다. 파일이 추가되거나, 다시 파싱되거나, 제거되지 않았다면 두 입력 테이블은 변경되지 않았고 해석 결과는 증명 가능할 정도로 동일합니다 — 따라서 건너뜁니다. 그 결과 Django의 무작동(no-op) 재인덱스가 7.5초에서 0.67초로 줄었고 그래프는 바이트 단위로 동일했습니다.

3. 임베딩 대신 PageRank

“어떤 get을 의미했나요?”는 구조적 질문입니다. 40개의 호출 사이트가 의존하는 get이 에이전트가 원하는 것이며, 호출 그래프는 이미 그것을 알고 있습니다. 따라서 심볼 순위는 호출 그래프에 대한 가중 PageRank입니다 — 안정적이고 설명 가능하며 비용이 없습니다. 모델도, 인덱스 빌드도, 벡터 저장소도 없습니다.

related_symbols는 같은 아이디어를 확장합니다. 한 심볼에 시드된 개인화 PageRank로 그래프를 무향으로 취급합니다. 함수를 변경하려 할 때 그 함수의 호출자와 피호출자가 모두 관련 컨텍스트이기 때문입니다. 이는 의미론적 검색의 구조적 대응물이며 임베딩이 필요 없습니다.

4. 도구는 토큰 예산 하에서 JSON이 아닌 Markdown을 반환합니다

소비자는 컨텍스트 윈도우입니다. 40개 심볼의 JSON 배열은 중괄호와 반복되는 키에 수천 개의 토큰을 소비하며, 모델은 어차피 그것을 재구성합니다. 여기 있는 모든 뷰는 엄격한 토큰 예산을 가진 컴팩트한 Markdown입니다.

중요한 점은 모든 잘림(truncation)이 명시적으로 알려진다는 것입니다. 87명의 호출자 중 20명만 표시 없이 전달받은 에이전트는 나머지 67명이 존재하지 않는다고 확신하고 무언가를 삭제할 것입니다.

5. 순회는 Python이 아닌 SQLite에서 실행됩니다

깊이 4의 who_calls는 재귀 CTE이므로 전체 순회가 SQLite의 C 루프 안에 머무릅니다. Django의 252k 엣지 그래프에서는 약 ~5ms입니다. 엣지 테이블을 Python으로 가져와 순회한다면 그렇게 되지 않을 것입니다.


벤치마크

실제 저장소, M-시리즈 노트북, 단일 프로세스. Cold = 처음부터 전체 인덱스; warm = 무작동 재인덱스.

저장소

파일

KLOC

심볼

엣지

Cold

Warm

DB

내부 해석

django

2,973

534

45,394

252,441

11.9s

0.67s

80 MB

83.2%

gin (Go)

98

24

1,610

9,179

0.32s

0.03s

2.5 MB

88.1%

flask

83

18

1,624

4,271

0.21s

0.03s

1.7 MB

87.4%

쿼리 지연 시간 (5회 중앙값, warm):

저장소

find_symbol

who_calls d3

blast_radius

architecture_overview

django

12.3ms

5.1ms

5.6ms

68.5ms

gin

0.4ms

0.4ms

0.5ms

1.2ms

flask

0.5ms

1.1ms

1.3ms

1.8ms

scripts/bench.py로 재현할 수 있습니다.


아키텍처

flowchart LR
  subgraph index["cartograph index"]
    W[walker<br/>git ls-files] --> P[tree-sitter<br/>+ .scm queries]
    P --> X[extract<br/>defs · refs · imports]
  end
  X --> DB[(SQLite<br/>symbols · refs<br/>edges · FTS5)]
  DB --> R[resolver<br/>rule cascade]
  R --> DB
  DB --> RK[PageRank<br/>Tarjan SCC]
  RK --> DB
  DB --> S[service facade]
  S --> V[views<br/>token-budgeted MD]
  V --> M[MCP server<br/>10 tools]
  V --> C[CLI]
  M --> A((coding agent))

모듈

책임

indexer/walker.py

파일 탐색 — 올바른 .gitignore 의미를 위해 git ls-files에 위임

indexer/languages.py

언어별 어댑터 하나: 확장자, 쿼리, 독스트링, 모듈 키, 임포트 해석

indexer/extract.py

AST → 심볼/참조/임포트, 언어 비종속적

queries/*.scm

tree-sitter 캡처 패턴 — 언어별 지식을 데이터로

graph/schema.sql

그래프: files, symbols, refs, edges, imports, FTS5

graph/resolver.py

신뢰도 캐스케이드

graph/algorithms.py

PageRank, 개인화 PageRank, 반복 Tarjan SCC, 계층화

graph/store.py

재귀 CTE 순회, 순위 검색, 집계

service.py

CLI와 MCP 서버가 어긋나지 않게 하는 단일 파사드

views.py

토큰 예산 Markdown

조합 쿼리 없이 스코프 다루기

queries/*.scm을 작게 유지하는 비결: 스코프는 절대 쿼리에 인코딩되지 않습니다. 캡처된 모든 정의는 tree-sitter 노드 id로 인덱싱되고, 참조의 포함 심볼은 parent 체인을 따라가며 심볼을 만날 때까지 찾습니다. 참조당 O(트리 깊이)이며 클로저, 메서드, 내부 클래스, 화살표 함수를 추가 비용 없이 처리합니다 — 형태별 패턴이 필요 없습니다.

언어 추가하기

LanguageAdapter(~40줄)를 서브클래싱하고 .scm 파일을 넣으세요. GoAdapter는 가장 짧은 완전한 예제입니다. 그러면 tests/test_queries.py가 문법에 대해 쿼리를 자동으로 컴파일하고 무언가를 캡처하는지 확인합니다.


개발

git clone https://github.com/GokulRaj2210/cartograph-mcp && cd cartograph-mcp
uv sync
uv run pytest -q          # 209 tests
uv run ruff check .
uv run mypy               # strict

CI는 Python 3.11/3.12/3.13(macOS 포함)에서 스위트를 실행한 다음 dogfooding을 합니다. 이 저장소를 인덱싱하고, 임포트 사이클이 있으면 실패하고, 무작동 재인덱스가 아무것도 다시 파싱하지 않음을 단언하고, 실제 stdio로 MCP 서버를 구동합니다. 또한 빌드된 wheel을 깨끗한 venv에 설치하고 그걸로 인덱싱합니다. 패키징된 .scm 파일은 wheel에서 빠뜨리기 쉽고 로컬에서는 알아차리기 불가능하기 때문입니다.

사이클 게이트는 이미 제 역할을 톡톡히 했습니다. 이 저장소에서 제가 만든 store → resolver → store 사이클을 잡아냈고, 게이트를 완화하는 대신 문제의 헬퍼를 옮겨서 수정했습니다.

주목할 만한 테스트

  • tests/test_queries.py — 모든 .scm은 이를 로드하는 모든 그래머에 대해 컴파일되어 무언가를 캡처합니다. JavaScript에서 유효한 패턴((class_heritage (identifier)))은 TypeScript에서 불가능한 패턴인데, TypeScript는 슈퍼타입을 extends_clause로 감쌉니다. 그 한 줄은 조용히 TypeScript 심볼을 0개 생성했습니다.

  • tests/test_incremental.py — 편집, 삭제 또는 파일 간 심볼 이동 후에도 오래된 엣지가 없습니다.

  • tests/test_resolver.py — 모든 규칙이 발동되며, 어떤 규칙도 자신의 신뢰도를 과장하지 않습니다.

  • tests/test_cli.py — 리더와 인덱서가 동시에 데이터베이스를 보유할 수 있습니다.

  • tests/test_docs.py — 생성된 데모 페이지는 태그 균형이 맞는 올바른 형식의 HTML이며, 이를 통해 min_confidence에서 발생한 Markdown 렌더러의 교차 태그 버그가 발견되었습니다.


제한 사항

솔직히 말하면, 정밀도를 과장하는 코드 인텔리전스 도구는 쓸모없는 것보다 더 나쁩니다:

  • 타입 추론 없음. self.conn.execute(...)conn의 타입을 알지 못하면 저장소 심볼로 해석될 수 없습니다. 이러한 것들은 unresolved에 속하게 되며, 내부 해석률이 ~85%일 때 남은 것들의 대부분을 차지합니다.

  • 동적 디스패치는 보이지 않습니다. getattr(obj, name)(), 데코레이터 레지스트리, DI 컨테이너는 엣지로 나타나지 않습니다.

  • 크로스 언어 엣지는 추적되지 않습니다. TypeScript 프런트엔드가 Python 엔드포인트를 호출하는 것은 두 개의 분리된 서브그래프입니다.

  • 정의만 있고 모든 참조는 아닙니다. 값으로 사용되는 심볼(콜백으로 전달되는)은 호출되는 심볼보다 그래프에서 더 약합니다.

로드맵: Rust 및 Java 어댑터, 언어 서버를 사용할 수 있는 곳에서 정확한 해석을 위한 선택적 LSP 강화, PR 범위의 영향 반경을 위한 --changed-since <ref> 모드.


왜 이것이 존재하는가

저는 대규모 저장소에서 코딩 에이전트의 가장 큰 약점 — 코드에 대한 구조적 모델이 없다는 점 — 이 더 큰 모델이나 벡터 데이터베이스보다는 정적 분석과 잘 설계된 도구 표면으로 해결될 수 있는지 알고 싶었습니다. 대체로, 가능합니다.

라이선스

MIT

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Give your AI agent a persistent map of your project's structure, dependencies, and bugs.

  • Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.

  • AI Agent with Architectural Memory. Impact analysis (free), tests and code from the graph (pro).

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GokulRaj2210/cartograph-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server