Self-hosted code search and relationship-aware code intelligence for humans and AI agents.
GraphNest is an experimental, self-hosted code search and context layer for engineering teams and AI agents. Authorized clients can search GitHub repositories, open files at the exact indexed commit, follow symbols across repositories, and inspect dependency and impact relationships without receiving direct access to the underlying search index.
Under the hood, GraphNest combines fast Zoekt search, SCIP code navigation, and a PostgreSQL relationship graph behind web, REST, and MCP interfaces.
Important
GraphNest is pre-1.0 pilot software. It is not production-ready, currently indexes default branches only, and has not yet been validated at production scale or certified against a live GitHub Enterprise Server or OpenShift environment. See Compatibility for the current boundaries.
| Capability | Description |
|---|---|
| Fast, scoped code search | Search authorized repositories through a server-controlled Zoekt backend. Clients never receive direct Zoekt access or choose raw Zoekt repository IDs. |
| Exact indexed revisions | Open files at the precise indexed commit. Search results are suppressed when Zoekt and PostgreSQL disagree about the current indexed SHA. |
| Cross-repository code navigation | Upload pre-generated SCIP indexes to navigate definitions, references, and implementations without running language indexers inside GraphNest. |
| Relationship-aware graph analysis | Explore bounded context, impact, and dependency paths directly from PostgreSQL. |
| Dependencies & Licenses inventory (opt-in) | Collect GitHub dependency-graph SBOMs on a schedule, preserve every original document byte-for-byte as an immutable snapshot, and browse or download the authorized repository inventory without depending on Zoekt, SCIP, or an indexed commit. |
| Human and agent interfaces | Use the embedded browser console, REST API, hosted Streamable HTTP MCP endpoint, or the graphnest-mcp stdio proxy. |
| GitHub-native repository management | Reconcile GitHub App installations, verify webhook signatures, queue default-branch indexing, support private CAs, and retain numeric GitHub repository identity across renames. |
| Durable identity and access | Use OIDC or GitHub OAuth browser sign-in, SCIM 2.0 provisioning, revocable API tokens, user and group repository assignments, administrative controls, and security audit events. |
| Pilot deployment tooling | Run locally with Docker Compose or deploy the single-node pilot with Helm. Releases publish multi-architecture images, an OCI chart, SBOMs, provenance, and GitHub attestations. |
An optional native enrichment binary supports Go, JavaScript, TypeScript/TSX, Java, Kotlin, and Rust. When configured, the indexer invokes it on the same archive snapshot; it is not a standalone worker and is not part of the default images or deployment. SCIP uploads remain a separate, language-indexer-independent navigation path.
flowchart LR
Client[Web UI / REST / MCP] --> Server[graphnest-server]
Server -->|GitHub App API| GitHub[GitHub.com or GHES]
GitHub -->|Signed webhooks| Server
Indexer[graphnest-indexer] -->|Download default-branch archive| GitHub
Server --> Postgres
Indexer --> Postgres
Postgres --> Indexer
Indexer --> Zoekt[(Zoekt index)]
Server --> Zoekt
Server --> Postgres
PostgreSQL is authoritative for repository metadata, authorization, queues, indexed-SHA state, graph artifacts, and graph queries. Zoekt is a private query store reached only through GraphNest's authenticated services. See Architecture and the accepted decisions under docs/adr.
GRAPHNEST_SEARCH_BACKEND defaults to zoekt. Durable deployments may set it to
github for low-volume degraded code search: every request remains
authorization-scoped, but results are best-effort, may be partial, and never
claim an exact indexed SHA. It does not automatically fall back between backends.
The architecture decision index records accepted and superseded design decisions.
| Interface | Location | Authentication |
|---|---|---|
| Browser console | / |
Development bearer token or durable OIDC or GitHub OAuth session |
| REST API | /v1/... |
Bearer token or, where supported, same-origin browser session |
| Streamable HTTP MCP | /mcp |
Bearer API token, or an OAuth access token obtained through the built-in authorization server |
| Stdio MCP proxy | graphnest-mcp |
Uses GRAPHNEST_SERVER_URL and GRAPHNEST_TOKEN |
| Health and observability | /healthz, /readyz, /metrics |
Intended for deployment health checks and monitoring |
REST routes accept exactly one bearer credential or browser session; mixed credentials are rejected. MCP remains bearer-only; with GRAPHNEST_MCP_OAUTH=true MCP clients obtain that bearer token themselves through OAuth 2.1 (see Operations).
The complete REST contract is available in docs/openapi.yaml.
The fixture profile is the fastest way to try GraphNest. It starts a deterministic test repository and Zoekt index while the server runs on the host.
- Go 1.27.1
- Git
- Docker with Docker Compose
jq- Internet access for Go tools and container images
git clone https://github.com/balcsida/graphnest.git
cd graphnest
make tools
docker compose -f deploy/compose/compose.yml --profile fixture up -d --waitThe fixture is indexed as repository fixture/repository with Zoekt repository ID 7.
In another terminal:
GRAPHNEST_LISTEN_ADDRESS=127.0.0.1:8080 \
GRAPHNEST_ZOEKT_URL=http://127.0.0.1:6070 \
GRAPHNEST_REPOSITORIES_FILE=deploy/compose/repositories.json \
GRAPHNEST_USER_TOKEN=graphnest-dev-user-token \
GRAPHNEST_ADMIN_TOKEN=graphnest-dev-admin-token \
GRAPHNEST_USER_REPOSITORIES=fixture/repository \
GRAPHNEST_ADMIN_REPOSITORIES=fixture/repository \
go run ./cmd/graphnest-serverOpen http://127.0.0.1:8080/ and sign in with:
graphnest-dev-user-token
The browser keeps this development token only for the current session.
curl --fail-with-body http://127.0.0.1:8080/v1/search \
-H 'Authorization: Bearer graphnest-dev-user-token' \
-H 'Content-Type: application/json' \
--data '{
"query": "GraphNestFixtureNeedle",
"repositories": ["fixture/repository"]
}'Requests for repositories outside the authenticated principal's scope return no matches rather than revealing whether those repositories exist.
docker compose -f deploy/compose/compose.yml --profile fixture downMCP clients that support Streamable HTTP can connect directly to:
http://127.0.0.1:8080/mcp
Send the same bearer token in the Authorization header. The core tools include code search and file discovery; durable mode additionally exposes symbol navigation and graph-backed analysis.
For a stdio-only MCP client, build the proxy:
go build -o /tmp/graphnest-mcp ./cmd/graphnest-mcp
GRAPHNEST_SERVER_URL=http://127.0.0.1:8080 \
GRAPHNEST_TOKEN=graphnest-dev-user-token \
/tmp/graphnest-mcpThe proxy appends /mcp automatically and does not connect to Zoekt directly.
GraphNest also ships optional graph-analysis skills for agent clients. Installation is explicit and does not happen during normal proxy startup:
/tmp/graphnest-mcp install-skills --root /path/to/repositoryThe installer writes GraphNest-owned content under .claude/skills/ and mirrors it to .agents/skills/ only when .agents/ already exists.
GraphNest stores SCIP indexes but does not generate them. Produce the .scip file in each repository's CI for the same 40-character lowercase commit SHA reported by GraphNest as indexed_sha, then upload it with an administrator token:
scip-go
curl --fail-with-body -X POST \
"https://graphnest.example/v1/scip/uploads?repository_id=101&commit=$GITHUB_SHA" \
-H "Authorization: Bearer $GRAPHNEST_ADMIN_TOKEN" \
-H 'Content-Type: application/vnd.scip+protobuf' \
--data-binary @index.scipUploads for any commit other than the repository's exact indexed SHA are rejected. The upload is ingested synchronously and returns 204 only after the index is committed, so a large index can take minutes; any reverse proxy or ingress in front of GraphNest needs a matching response timeout (see the Helm chart documentation). Cross-repository navigation can use manually supplied package URLs or metadata refreshed from GitHub's dependency graph. The exact endpoints, limits, and response schemas are defined in the OpenAPI contract.
A CI job should not hold a long-lived administrator token. Instead, a trusted broker that owns an administrator API token can delegate a narrower one per job with POST /v1/admin/api-tokens: the delegated token belongs to the same user, is restricted to a non-empty subset of the broker token's repository ceiling (typically the one repository being indexed), and must expire within one hour. Only administrator API tokens may delegate, and a delegated token cannot delegate again, so a leaked job token cannot renew itself past its own expiry; browser sessions keep using /v1/account/api-tokens.
curl --fail-with-body -X POST "https://graphnest.example/v1/admin/api-tokens" \
-H "Authorization: Bearer $GRAPHNEST_BROKER_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"repository_ids":[101],"expires_at":"2026-08-01T00:15:00Z"}'With GRAPHNEST_SUPPLY_CHAIN=true in durable mode, graphnest-server collects each managed repository's dependency-graph SBOM export from GitHub on a jittered schedule (GRAPHNEST_SUPPLY_CHAIN_INTERVAL, default 24h), preserves the original SPDX 2.3 JSON document and its SHA-256, normalizes component occurrences and relationships into an immutable snapshot, and serves them under /v1/supply-chain/... and the embedded page at /supply-chain. The module is disabled by default; enabling it centrally requires no change to any repository.
What the inventory is and is not:
- A GitHub dependency-graph export is a timestamped observation of the default branch. The endpoint has no ref selector, so snapshots report
subject_assurance: unknown; GraphNest never copies the indexed or current HEAD into a snapshot. - GitHub Enterprise Server does not populate dependency license fields;
license_declared_raw/license_concluded_raware preserved verbatim (typicallyNOASSERTION) and are never mapped to a license. Exact-version license evidence comes only from registry routes you configure (GRAPHNEST_SUPPLY_CHAIN_REGISTRY_{NPM,NUGET,MAVEN}_URLand companion secret-file settings); without a route no license traffic is produced, and a private route never falls back to a public registry. SPDX expressions are parsed against the pinned SPDX License List 3.27.0 with AND/OR/WITH structure preserved;NOASSERTION,NONE,UNLICENSED, unknown identifiers, license files, and URLs stay what they are. - Components without a purl or version stay visible. Dependency scope (
root/direct/transitive) is derived only from resolvedDEPENDS_ONedges leaving a described root; a flattened list yieldsunknown, neverdirect. - A failed refresh (403, 404, rate limit, malformed or oversized document, outage) records a collection attempt and leaves the last successful snapshot in place; the status reports
collection: failedalongside the retained inventory. - Inventory eligibility is repository authorization alone. It works for repositories with no Zoekt index, no SCIP upload, and no graph enrichment, and inventory work never blocks lexical indexing.
License review is a separate, auditable layer: reviewers with a repository-scoped grant record human conclusions and approve/reject/exception decisions against the exact evidence they saw (a changed evidence fingerprint is refused), versioned policies are evaluated over the SPDX expression tree (the shipped policy is a labelled example, and unknown licensing never auto-approves), and three read-only MCP tools expose the inventory to agents through the same authorization as REST.
SBOMs produced elsewhere (Syft, ORT, or any tool writing SPDX 2.3 JSON or CycloneDX 1.6 JSON) can be imported into separate import:<subject>:<label> streams with POST /v1/supply-chain/imports; the uploader is recorded apart from the producer the document claims, and a derived SPDX export links back to the preserved original. Portfolio views (/v1/supply-chain/overview, /components, /facets, exports, comparison) aggregate only over the caller's authorized repositories and name every denominator.
Every read resolves the live principal's repository scope before any inventory row is touched; snapshot and job identifiers outside that scope are indistinguishable from missing ones. Manual refresh (POST /v1/supply-chain/repositories/{id}/refresh) only enqueues a bounded background job and requires administrator access. The published snapshot is also projected into the existing GitHub-sourced SCIP package mappings; manual mappings are never touched. See Operations and ADR-0017.
Static fixture mode is intentionally small. Durable mode adds PostgreSQL-backed repository state, GitHub App reconciliation, verified webhook ingestion, queued indexing, exact-SHA file reads, identity management, and graph analysis.
A durable deployment consists of:
graphnest-serverfor the web UI, REST, MCP, authentication, authorization, and GitHub reconciliation;- one
graphnest-indexerfor leased default-branch indexing and Zoekt publication; - PostgreSQL as the authoritative state store; and
- Zoekt as private query infrastructure.
The Compose deployment requires application and node images, PostgreSQL, GitHub App credentials, and the server settings documented in Operations:
docker compose \
-f deploy/compose/compose.yml \
-f deploy/compose/durable.yml \
--profile durable \
up -d --wait| Purpose | Mechanism |
|---|---|
| Local fixture access | Distinct development-only user and administrator bearer tokens |
| Browser sign-in | OIDC or GitHub OAuth Authorization Code flow with PKCE and an opaque, HttpOnly GraphNest session |
| REST and MCP access | Revocable bearer API tokens; /mcp remains bearer-only |
| MCP client sign-in | Optional OAuth 2.1 authorization server (GRAPHNEST_MCP_OAUTH): dynamic client registration, PKCE, consent page, hour-long access tokens with rotating refresh |
| Directory provisioning | Optional SCIM 2.0 endpoint protected by a dedicated secret-file token |
| Emergency administration | Disabled-by-default local recovery flow provisioned offline with graphnest-admin |
Authorization is enforced by the server against current repository IDs and directory state. Repository names are selectors, not security identities. Deactivated users and revoked credentials are denied on their next request.
GitHub OAuth uses a dedicated OAuth App per environment, separate from the GitHub App used for repository access. Configure GRAPHNEST_PUBLIC_URL, GRAPHNEST_OAUTH_GITHUB_CLIENT_ID, and GRAPHNEST_OAUTH_GITHUB_CLIENT_SECRET_FILE, then register https://<public-host>/auth/oauth/github/callback. The flow requests no scope and uses the access token once for GET /user; the token is then discarded and cannot authenticate MCP. GitHub Enterprise Server OAuth remains unverified.
Optionally, GraphNest can inherit repository access from GitHub instead of SCIM. With GRAPHNEST_OAUTH_GITHUB_ACCESS_SYNC=true, the OAuth client must be the GitHub App's own OAuth credential; each sign-in then provisions the user on first use and replaces their GitHub-derived grants with the indexed repositories that user can access through the App's installations. Administrator remains an explicit GraphNest role. See Operations.
OIDC, GitHub OAuth, SCIM, API-token administration, audit events, and break-glass recovery require durable mode. See Operations and the Threat model before exposing the service.
The chart under deploy/helm/graphnest targets Kubernetes 1.25 or newer and models a generic single-node pilot. It expects operator-managed PostgreSQL and existing Kubernetes Secrets; it does not install a database or place plaintext credentials in chart values.
Released OCI charts embed immutable application and node image digests. For the current chart version:
Deployments created before the GraphNest rename require fresh GraphNest configuration and a fresh GraphNest installation. This is not an in-place upgrade; GraphNest does not automatically discover or mutate previous deployment state. The helm upgrade --install example below applies only to upgrades between GraphNest releases.
helm pull oci://ghcr.io/balcsida/graphnest/charts/graphnest --version 0.2.0
helm upgrade --install graphnest graphnest-0.2.0.tgz \
--namespace graphnest \
--create-namespace \
--values my-values.yaml \
--wait \
--timeout 15mReview the Helm chart documentation for required images, Secrets, storage, ingress, network policies, OIDC, GitHub OAuth, SCIM, monitoring, and recovery procedures. Follow the archive and PostgreSQL graph migration when upgrading an older deployment. Release notes contain immutable artifact references and attestation-verification commands.
- GraphNest is pre-1.0 pilot software and makes no stable compatibility promise.
- Only default branches are indexed.
- The default GHES contract targets GitHub Enterprise Server 3.17 with REST API version
2022-11-28; this has not been certified against a live GHES deployment. - Kubernetes, OpenShift, backup and restore, upgrade and rollback, ingress, and production-scale capacity still require environment-specific validation.
- Optional native graph scanning is not equivalent to a full language server or language-specific indexer.
- GraphNest does not currently provide embedding-based semantic search.
- PostgreSQL graph data follows the same backup and recovery policy as other durable repository state.
Read Compatibility, Benchmarking, and Operations before planning a pilot.
Build the commands and local images with:
make build
make image-testRun the main verification suites with:
make fmt lint staticcheck govulncheck
make test test-race integration e2e
make openapi-check compose-test helm-lint helm-testCI additionally exercises scanner grammar compatibility, UI smoke tests, and release packaging. Some targets download pinned tools and require Docker.
| Document | Purpose |
|---|---|
| Architecture | Service boundaries, authorization flow, indexing, and graph queries |
| Operations | Local and durable operation, recovery, identity, and graph runbooks |
| OpenAPI | Canonical REST request, response, security, and limit contract |
| Helm chart | Kubernetes configuration, Secrets, storage, networking, and installation |
| Compatibility | Supported contracts, platforms, languages, and unverified boundaries |
| Threat model | Protected assets, security controls, and known limits |
| Benchmarking | Measurement guidance for pilot sizing |
| Release process | Signed tags, images, OCI chart, attestations, and release verification |
| Implementation report | Delivered milestones, verification evidence, risks, and deferred work |
| Dependency pinning | Reproducibility and pinned dependency policy |
| Archive and graph migration | Upgrade, verification, cleanup, and rollback procedure |
See the architecture decision index for the complete decision history.
Read CONTRIBUTING.md before proposing a change. Keep changes within an accepted milestone, include tests for behavior changes, and preserve the boundary that Zoekt is private implementation infrastructure.
Use GitHub Issues for reproducible bugs and feature discussions, and read SUPPORT.md for the project's support boundaries. Participation is governed by the Code of Conduct. Report suspected vulnerabilities through the private process in SECURITY.md, not through a public issue.
GraphNest is licensed under the Apache License 2.0.
