version-aware-code MCP server
2026년 11월 13일 커밋AI agent이 정확한 컨텍스트를 바탕으로 코딩 작업을 수행할 수 있도록 버전 범위 기반의 코드 검색, 콜 그래프 분석 및 소스 코드 검색 기능을 제공합니다.
다른 이름 tc3oliver/version-aware-code-mcp
- 스타
- 1
- Tools
- 7
- 다운로드, 30일
- —
- 최신 버전
- —
- 마지막 커밋
- 2026년 11월 13일
- 라이선스
- Apache-2.0
version-aware-code MCP란?
version-aware-code은(는) MCP server입니다. AI agent이 정확한 컨텍스트를 바탕으로 코딩 작업을 수행할 수 있도록 버전 범위 기반의 코드 검색, 콜 그래프 분석 및 소스 코드 검색 기능을 제공합니다. 7개 도구를 제공합니다. 예: list_contexts, search_code, trace_calls.
프로젝트 README에서 생성된 요약
version-aware-code MCP server 정보
AI 코딩 agent를 사용하는 개발자는 version-aware-code-mcp를 통해 어시스턴트가 코드베이스의 정확한 버전에서 작업하도록 보장할 수 있습니다. 이 server는 모든 요청에 대해 엄격한 버전 범위를 적용하여, agent가 잘못된 branch나 commit에서 코드를 가져오는 실수를 방지합니다. 이 server는 AI 클라이언트와 검색을 위한 Zoekt, 콜 그래프 분석을 위한 codebase-memory-mcp, 소스 검색을 위한 git과 같은 로컬 엔진 사이의 가교 역할을 합니다.
사용자는 설정 파일을 사용하는 정적 모드 또는 repository 클로닝과 컨텍스트 생성을 자동으로 처리하는 관리 모드로 server를 설정할 수 있습니다. server는 stdio 또는 streamable HTTP를 통해 로컬에서 실행되며, LLM API key나 임베딩이 전혀 필요하지 않습니다. 모든 도구 결과에는 repository, branch, revision, file, line number와 같은 명시적인 근거가 포함되어 있어 agent가 검색 결과의 컨텍스트를 검증할 수 있습니다. 이는 레거시 유지보수나 특정 릴리스 간의 구조적 변경 사항을 비교하는 작업에 이상적입니다.
활용 사례
- 1레거시 릴리스의 버그 수정agent가 특정 함수를 검색하고 지정된 릴리스 branch 내에서만 소스 코드를 가져오도록 하여, main branch의 로직을 사용하는 것을 방지할 수 있습니다.
- 2버전 간 구조적 변경 사항 분석개발자가 AI에게 두 가지 다른 코드 버전에서 특정 심볼의 콜 그래프를 비교하도록 요청할 수 있습니다.
- 3코드 컨텍스트 정확성 검증AI agent가 제공된 메타데이터를 사용하여 분석 중인 코드 스니펫이 요청된 정확한 commit에 속하는지 확인할 수 있습니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
version-aware-code MCP 도구 7
README에서 감지됨list_contextssearch_codetrace_callsget_codecompare_codecompare_callssearch_history
| list_contexts | — | |
| search_code | — | |
| trace_calls | — | |
| get_code | — | |
| compare_code | — | |
| compare_calls | — | |
| search_history | — |
자동 검사
2026년 11월 14일에 실행됨전체 보고서 →아직 인증되지 않았습니다. 서버는 80점 이상의 점수, 심각한 결함 없음, 그리고 검사에 충분한 데이터를 갖추어야 합니다. 검사 방식 · 점수를 높이기 위해 수정할 항목
- 소스 저장소 연결 및 접근 가능tc3oliver/version-aware-code-mcp is public and reachablegithub.com/tc3oliver/version-aware-code-mcp ↗통과
- 패키지가 이 저장소를 소스로 명시검사되지 않음
- 빌드 출처(Sigstore 증명)검사되지 않음
- 오픈 소스 라이선스 (OSI)Apache-2.0spdx.org/licenses/Apache-2.0.html ↗통과
- 최근 1년 내 커밋 또는 릴리스Last commit or release 2026-11-13github.com/tc3oliver/version-aware-code-mcp ↗통과
- OpenSSF Scorecard검사되지 않음
- 패키지 이름이 인기 패키지와 유사하지 않음검사되지 않음
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
- 모델에 대한 숨겨진 지침 없음7 tool descriptions and the README read; no override phrases, credential paths or hidden characters통과
- 파괴적인 도구에 라벨 표시됨No tool named like delete, drop or exec통과
- 원격 엔드포인트가 MCP 핸드셰이크에 응답검사되지 않음
README
GitHub에서 보기version-aware-code-mcp
Version-aware code intelligence for AI coding agents. Combines branch-aware code search with structural code graph analysis through MCP.
Why
Agents search the right code in the wrong version.
Ask an agent to fix a bug in release 2.x and it will happily grep its way into a
file from main, or into a checkout that stopped tracking the branch three
merges ago. The snippet looks plausible. The function name matches. Nothing in
the result says which version it came from, so the mistake is invisible until it
ships.
This server makes the version explicit and enforced. Every request names a context — a repository, a branch, and a revision — and every result is confined to it:
- Search is restricted to that context's branch.
- The call graph queried is the one built for that context's revision.
- Source is read at that revision, and a mismatch fails the request instead of quietly returning the wrong file.
- Every result carries the repository, branch, revision, file, and line it came from, so the answer can be checked rather than trusted.
The server does not answer questions. It supplies search, graph, source, version, and evidence. Your agent does the reasoning.
Architecture
vacmcp sits between your agent and the engines that already index your code. It adds one thing to them: a context, an id that names one repository, one branch, one revision and one call graph. Every tool call takes a context id — two, when it compares two versions — and nothing it returns comes from outside that scope.
your agent
│ MCP 2026-07-28, over STDIO or Streamable HTTP
▼
vacmcp ──── list_contexts search_code trace_calls get_code
│ compare_code compare_calls
│
│ resolve the context id against the configuration:
│ repository, branch, revision, graph reference
│
├── SearchProvider ──▶ Zoekt (local web server, queried over HTTP)
├── GraphProvider ──▶ CBM (codebase-memory-mcp, local subprocess)
└── SourceProvider ──▶ git (local clone, read at the revision)
Neither engine is vendored or forked. Zoekt indexes several branches into one index and picks the version at query time; codebase-memory-mcp holds one graph per project, so each version gets a project of its own. vacmcp resolves the context, hands that scope to the provider, and puts the context and the evidence back on every answer.
Features
- Version-scoped search. A query is confined to the repository and branch of the context it names; it cannot widen its own scope.
- Version-scoped call graph. A trace runs in the graph built for that context's revision, never in another version's.
- Source read at a revision. Lines come out of the commit the context declares, not out of whatever the working tree is on.
- Comparison between two versions. One file or one symbol's call graph, read in two contexts and reported as two sides, so neither version's evidence is mixed into the other's.
- Evidence on every result. Repository, branch, revision, file and line, so an answer can be checked instead of trusted.
- Local-first. Local Zoekt, local codebase-memory-mcp, local git. No LLM API key, no embeddings. See Security.
Quick Start
With Docker
Needs Docker and nothing else. The stack brings up a Zoekt web server, builds a
graph per release and serves vacmcp against a demo repository baked into the
images: release/v1's Process() calls LegacyHandler(), release/v2's calls
NewHandler(), and the two contexts demo-v1 and demo-v2 are configured over
them.
docker compose -f docker/compose.yaml up -d --build
docker compose -f docker/compose.yaml exec vacmcp \
vacmcp doctor --config /etc/vacmcp/config.yaml
vacmcp then serves Streamable HTTP on http://127.0.0.1:8080, with the contexts
demo-v1 and demo-v2. The tool call at the end of the next section works
against it with demo-v2 as the context. Both published ports are bound to
loopback. Tear it down with:
docker compose -f docker/compose.yaml down -v
With your own repository
You need Go 1.26, git, and the two engines vacmcp queries. Neither engine ships with this project:
go install github.com/sourcegraph/zoekt/cmd/zoekt-git-index@latest
go install github.com/sourcegraph/zoekt/cmd/zoekt-webserver@latest
go install github.com/sourcegraph/zoekt/cmd/zoekt@latest # optional, to inspect the index
For the graph, download
codebase-memory-mcp
0.11.0 or newer for your platform and put the binary on your PATH.
Install vacmcp. Every release publishes an archive per platform — linux, macOS
and Windows, amd64 and arm64 — next to a SHA256SUMS file covering all of
them, on the releases page:
sha256sum -c SHA256SUMS --ignore-missing
tar -xzf vacmcp_v0.8.0_linux_amd64.tar.gz
./vacmcp version # v0.8.0, the release this binary was built from
Or build it from source, which reports 0.0.0-dev because no release built it:
git clone https://github.com/tc3oliver/version-aware-code-mcp
cd version-aware-code-mcp
make build
Index the branches you want to search. One index holds them all:
zoekt-git-index -index ~/.vacmcp/zoekt-index \
-branches release/1.x,release/2.x /path/to/backend
Build one graph per version. codebase-memory-mcp indexes a directory rather than a revision, so each version needs a checkout of its own — a worktree does:
git -C /path/to/backend worktree add --detach /tmp/backend-v1 release/1.x
git -C /path/to/backend worktree add --detach /tmp/backend-v2 release/2.x
codebase-memory-mcp cli index_repository --repo-path /tmp/backend-v1 --name backend-v1
codebase-memory-mcp cli index_repository --repo-path /tmp/backend-v2 --name backend-v2
Write a configuration naming both — config/example.yaml is a working starting
point, and Context Configuration explains the fields:
providers:
zoekt:
url: http://127.0.0.1:6070
cbm:
command: codebase-memory-mcp
repositories:
backend:
path: /path/to/backend
contexts:
backend-v1:
repository: backend
branch: release/1.x
revision: 8af31e2
graph_ref: backend-v1
backend-v2:
repository: backend
branch: release/2.x
revision: 94cb821
graph_ref: backend-v2
revision is a commit: git -C /path/to/backend rev-parse release/1.x prints
the one to write down.
The repositories key has to be the name Zoekt indexed the repository under,
which it derives from the clone's remote and falls back to the directory name
for. Get it wrong and searches come back empty rather than failing, so check it
against the index:
zoekt -index_dir ~/.vacmcp/zoekt-index -r -l . | head -1 # prints repository/path
Start Zoekt, check the setup, and serve:
zoekt-webserver -index ~/.vacmcp/zoekt-index -listen 127.0.0.1:6070 -rpc &
./bin/vacmcp doctor --config vacmcp.yaml
./bin/vacmcp serve --config vacmcp.yaml
doctor reports one row per dependency and one per context, and exits non-zero
if any of them did not pass:
MCP SDK OK go-sdk v1.7.0, 6 tools
Config OK vacmcp.yaml: 1 repositories, 2 contexts
Zoekt OK http://127.0.0.1:6070
CBM OK 0.11.0
Git repo OK backend
Contexts
backend-v1 OK backend release/1.x 68c19fdb264bad80cf220f672f89dc90478a9d66
backend-v2 OK backend release/2.x 7b05c6aeb7544cc7fd2b69c56c89c9bcfd46a1a2
Point your MCP client at the server as below. To check it answers without one, Streamable HTTP takes a JSON-RPC call over plain HTTP:
curl -sS http://127.0.0.1:8080 \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_code",
"arguments":{"context":"backend-v2","query":"NewHandler"}}}'
Managed Mode
Everything above is the static mode: you index, you check out, you write the
configuration, and vacmcp serve --config FILE serves what you wrote. Managed
mode does that part for you. vacmcp repo add clones a repository into a data
directory, vacmcp context create pins a ref to a commit and builds that
commit's index and graph, and vacmcp serve --managed serves the contexts that
came out ready. Same engines, same seven tools, same isolation — no configuration
file and no zoekt-git-index, git worktree or index_repository by hand.
vacmcp repo add backend --url git@git.example.com:team/backend.git
vacmcp context create backend-v1 --repo backend --ref release/1.x
vacmcp context create backend-v2 --repo backend --ref release/2.x
context create resolves the ref once and pins the full commit SHA it found.
That pin never moves: vacmcp repo sync fetches new commits, and the contexts
you already have keep answering out of the revisions they were created with. To
serve a newer commit, create another context — a context is immutable, so a
second version is a second name, never an edit of the first.
A context can name several repositories, one --repo/--ref pair each, and
each of them is pinned to its own commit:
vacmcp context create stack-v1 --repo backend --ref release/1.x \
--repo frontend --ref release/3.x
The nth --ref belongs to the nth --repo. Such a context is built and removed
as one thing: it reaches READY only when every repository in it has been
checked out, indexed, given its graph and verified, and one that fails anywhere
is FAILED whole rather than half servable.
The query tools answer in such a context by taking a repository argument, one
of the ones list_contexts reports for it. search_code covers every repository
of the context without it and one of them with it; the other four answer in
exactly one repository, so they require it and refuse the call with
INVALID_ARGUMENT naming the selectable repositories rather than answering out
of the first one and leaving the rest silently outside the scope. A repository
the context does not name is refused too — it selects one of the versions the
configuration granted, it never reaches past them.
Everything lives under ~/.vacmcp unless --data-dir says otherwise, including
the search index. Start Zoekt over it, check the installation, and serve:
zoekt-webserver -index ~/.vacmcp/zoekt -listen 127.0.0.1:6070 -rpc &
vacmcp doctor --managed
vacmcp serve --managed
$ vacmcp context list
backend-v1 backend 8af31e2c8d0a4f1b6e5d3c2a90b7f4e1d6c8a3b5 READY
backend-v2 backend 94cb821f7a6e5d4c3b2a1908f7e6d5c4b3a29187 READY
stack-v1 backend 8af31e2c8d0a4f1b6e5d3c2a90b7f4e1d6c8a3b5 READY
stack-v1 frontend 1d0c9b8a7f6e5d4c3b2a19087f6e5d4c3b2a1908 READY
One row per repository a context names, so a context over one reads as it always has and one over several says which commit each of its repositories is pinned to.
The commands, in full:
| Command | What it does |
|---|---|
vacmcp repo add NAME --url URL |
clone a repository into the data directory |
vacmcp repo list / status NAME |
report the managed repositories |
vacmcp repo sync NAME / --all |
fetch remote refs, moving no pinned revision |
vacmcp repo remove NAME |
forget a repository and delete its clone |
vacmcp context create NAME --repo REPO --ref REF [--repo REPO --ref REF ...] |
pin one ref per repository to a commit and build its index and graph |
vacmcp context list / status NAME |
report the managed contexts and their state |
vacmcp context verify NAME |
re-run the readiness checks, changing nothing |
vacmcp context retry NAME |
rebuild a context that did not reach READY |
vacmcp context remove NAME |
forget a context, its worktree and its graph |
vacmcp serve --managed [--data-dir DIR] [--zoekt-url URL] [--cbm-command CMD] |
serve the ready contexts |
vacmcp doctor --managed [--data-dir DIR] |
check the managed repositories and contexts |
Only these commands reach a network, and only from your shell: creating,
syncing and removing repositories and contexts is not exposed as an MCP tool, so
an agent connected to the server cannot make it clone or delete anything.
vacmcp holds no credential either — git authenticates the clone the way it
always does, through your SSH agent, ~/.ssh/config or a credential helper, and
a URL with a secret embedded in it is refused rather than stored.
A running vacmcp serve --managed serves the contexts it read when it started,
and nothing changes them underneath it. context create, context retry,
context remove and repo remove are refused while one is running rather than
done behind its back — a server left answering out of a worktree, an index or a
graph that has been deleted is exactly what this project must never do. To put a
change into service, stop the server, run the command, and start it again:
# stop the running `vacmcp serve --managed`
vacmcp context create backend-v3 --repo backend --ref release/3.x
vacmcp serve --managed
vacmcp repo sync is not refused and needs no restart: it fetches, and a fetch
moves no pinned revision, so a running server keeps answering exactly as it did.
The commits it brings in reach a server through the next context create and the
restart that follows. Reading is never refused either — context list, status,
verify, repo list, status and doctor --managed all run while a server
does, so nothing stops you diagnosing one.
On Unix-like systems both of these hold across processes: repository lifecycle operations take turns, and a management command really is refused while a managed server runs.
On Windows, Managed Mode currently provides in-process locking only, and that
applies to the server as much as to the commands: vacmcp serve --managed
starts without holding a cross-process lock on its data directory, so the
refusal above does not happen there — a context or repo command run against
the directory it is serving is allowed through and can change the artifacts
underneath it. Do not run vacmcp management commands against a data directory
that another vacmcp management command, or a running vacmcp serve --managed,
is using. All three commands say so themselves on Windows, on stderr, at the
moment they start.
Static Mode is unaffected.
Already using vacmcp serve --config FILE? Nothing changes for you. Managed
mode is additive and entirely optional: static configuration files are still
supported exactly as before, there is no migration to do, and the two modes are
chosen per server with --config or --managed.
MCP Configuration
vacmcp speaks MCP 2026-07-28 through the official Go SDK. Streamable HTTP runs
in stateless mode, which is what the protocol version needs: there is no session
and no initialize handshake, and a client discovers the server with
server/discover. The server advertises tools and nothing else — it sends no log
notifications, so it does not claim a capability it does not have.
STDIO, for a client that runs vacmcp as a subprocess. The shape below is the common one; use whatever your client's configuration file wants:
{
"mcpServers": {
"vacmcp": {
"command": "/path/to/bin/vacmcp",
"args": ["serve", "--stdio", "--config", "/path/to/vacmcp.yaml"]
}
}
}
Streamable HTTP, for a server shared by several clients or run in a container:
vacmcp serve --config vacmcp.yaml --address 127.0.0.1:8080
The listen address is --address if you pass it, otherwise server.address
from the configuration file, otherwise 127.0.0.1:8080. The default is loopback
on purpose: this server reads your source, so reaching it from the network is
something you have to ask for.
The other commands take the same configuration:
vacmcp validate --config FILE load and check the configuration
vacmcp contexts --config FILE list the configured contexts
vacmcp doctor --config FILE check every dependency and context
vacmcp version print the version
Context Configuration
A context is the unit of version isolation. It names:
| Field | What it decides |
|---|---|
repository |
which repository, by its key under repositories |
branch |
the branch search_code is confined to |
revision |
the commit get_code reads at, and the version the context claims |
graph_ref |
the codebase-memory-mcp project trace_calls runs in |
Two contexts over one repository is the normal case, and the reason the server
exists: the same file name means different code in release/1.x and
release/2.x, and every call has to say which one it means. revision may be
any revision git resolves, but a commit SHA is what actually pins a version — a
branch name moves.
graph_ref is a codebase-memory-mcp project, and a project is one indexed
graph. Two contexts at different revisions may not share one: that would trace
calls against the other version's graph, so the configuration is rejected rather
than loaded. The graph reference is internal — it never appears in tool output.
Parsing is strict. An unknown field, a duplicate key, a context pointing at a repository that is not declared: each is an error naming the context and the field, not a setting silently ignored. There are no LLM settings of any kind in this file, by design.
Check a configuration before serving with it:
vacmcp validate --config vacmcp.yaml # vacmcp.yaml: ok, 2 contexts
vacmcp contexts --config vacmcp.yaml # id, repository, branch, revision
Multi-Repo Context
A context can name one repository or several. One naming several is a workspace: each repository it lists — a member — is pinned to its own revision, exactly as a single-repository context pins one. A context over one repository is a workspace with exactly one member, and answers exactly as it did before this feature existed: same arguments, same wire shape, same error codes. Nothing about that case changes; multi-repo is what a context does once it names a second repository.
contexts:
stack-v1:
members:
- repository: example/backend
branch: release/1.x
revision: 8af31e2
graph_ref: backend-v1
- repository: example/frontend
branch: release/3.x
revision: 1a2b3c4
graph_ref: frontend-v3
config/example.yaml has this alongside the single-repository contexts above
it, and vacmcp validate loads both the same way. Managed Mode builds the same
shape from the CLI, with one --repo/--ref pair per member:
vacmcp context create stack-v1 --repo backend --ref release/1.x \
--repo frontend --ref release/3.x
list_contexts reports stack-v1 as {"id": "stack-v1", "members": [...]}
instead of the flat repository/branch/revision a single-member context
carries — the wire shape follows the member count, one member emitting exactly
what v0.4.0 did and several emitting the array instead, never both at once.
Every result's context block and every citation follow the same rule: a
single-member answer's evidence carries no repository or revision of its
own, as before, and a several-member answer's evidence — and search_code's
matches — each carry the repository and revision they were found in. Only
search_code and list_contexts ever emit the several-member shape; the other
four narrow to one member before they answer, so their context block is always
the flat one, and so is every example in Tools below.
repository is the one argument a request may use to say which member it
means, and it only narrows what the context already names — a repository the
context does not have is refused, never reached:
| Tool | One member | Several members |
|---|---|---|
search_code |
unchanged | searches every member unless repository narrows it to one |
get_code |
unchanged | repository required — two members could both have the path, so nothing else says which one was meant |
trace_calls |
unchanged | repository required — a call graph is one repository's own, so there is no walk without it |
compare_code / compare_calls |
unchanged | repository required on both sides, and it must name a repository both contexts have |
search_history |
unchanged | searches every member unless repository narrows it to one, exactly as search_code does |
Nothing is inferred even when only one member happens to have a match today: the same request would silently change meaning the day a second member acquired the same path or symbol, which is the ambiguity this server always refuses rather than guesses at.
Limitations
v0.5.0 does not:
- Build, infer or claim a call edge between two repositories.
trace_callswalks the one member's graphrepositorynames and nothing else — there is no attempt to connect a caller in one repository to a callee in another. - Compare a whole workspace to another.
compare_codeandcompare_callscompare one repository at a time, picked out byrepositoryon both sides; there is no member-added/member-removed report and no workspace-level diff. - Deduplicate artifacts two workspaces share. Two Managed Mode workspaces naming the same repository at the same revision each get their own index and graph.
- Detect a rename or move. Unchanged since v0.4.0: a comparison matches paths literally, in a multi-repo workspace exactly as in a single-repository one.
- Resolve symbol identity semantically. Unchanged since v0.4.0: a comparison matches the symbol name that was asked for, textually, one repository at a time.
version-aware-code MCP 질문
version-aware-code MCP는 어떤 도구를 제공하나요?
7개 도구를 제공합니다. 예: list_contexts, search_code, trace_calls, get_code, compare_code.
이 server가 질문에 대한 답변을 제공하나요?
아니요, server는 검색, 그래프, 소스, 버전 및 근거 데이터를 제공하며, 추론은 AI agent가 수행합니다.
이 server를 사용하려면 LLM API key가 필요한가요?
아니요, 이 server는 로컬 우선 방식으로 작동하며 LLM API key나 임베딩이 필요하지 않습니다.
서로 다른 브랜치 간의 코드 정확성을 어떻게 보장하나요?
버전 범위 기반의 검색과 콜 그래프를 강제하여, 모든 결과에 특정 repository, branch, revision 메타데이터가 포함되도록 보장합니다.