Project Knowledge MCP Server
Analyzes GitLab group projects to extract purpose, I/O, dependencies, and database tables, serving this knowledge via MCP tools and a read-only HTML visualization.
README
Project Knowledge MCP Server
An MCP server that builds and serves structured knowledge about projects in a GitLab group.
It runs in a single OS process and exposes two surfaces:
- MCP over stdio — eight tools that an MCP client can call to query projects and trigger refreshes.
- Read-only HTML visualization on
http://127.0.0.1:{port}— diagrams of project profiles, shared dependencies, and purpose conflicts.
Both surfaces read from the same Knowledge_Store (a single SQLite file with snapshot-based atomic-pointer-swap semantics) so readers always see a consistent view, even while a refresh is in flight.
What does the analysis? Static inspection in Python (no LLM). For every project, four sub-analyzers run: purpose (READMEs, GitLab description, manifests, top-level docstrings), I/O extractor (HTTP routes, scheduled tasks, message consumers/publishers, file I/O, CLI entrypoints), external services (HTTP clients, brokers, object stores), and database tables (SQL/ORM references, read/write modes). Source code is fetched at the configured
Analysis_Branch(defaultuat).
Go analyzer support
Go repositories are analyzed in-process with the same depth as Python, JavaScript/TypeScript, and Java repositories. No Go toolchain is invoked at runtime; a hand-written Python tokenizer reads .go source directly. The Go layer activates automatically when a RepositoryContents snapshot contains at least one .go file or a repo-root go.mod — there is no configuration to enable.
What gets detected
| Sub-analyzer | Go patterns recognized |
|---|---|
| Purpose | module <path> line in go.mod (and any adjacent //-comment), package doc comment on repo-root, cmd/main.go, or cmd/<name>/main.go files. Module-path host/org prefixes (github.com/acme/) are stripped so the binary name remains. |
| I/O extractor | http.HandleFunc / http.Handle / mux.HandleFunc / mux.Handle with Go 1.22 method-prefixed pattern support; cron.New(...) + AddFunc/AddJob (six- or five-field schedules), time.NewTicker, time.AfterFunc; *activemq.Receiver.Subscribe(..., &domain.SubscriberConfig{Destination: ...}, ...) and *activemq.Sender.SendMessage(..., &domain.Message{Destination: ...}); os.Open / os.ReadFile / os.Create / os.WriteFile / os.OpenFile (flag bitmask classified); flag.* registrations; cmd/<name>/main.go / cmd/main.go / root main.go binary names. |
| External services | fec_pool_service/pb clients (ExecuteQuery), esb-go-libs/dbadapter clients (PoolExecuteQuery), and ActiveMQ broker connections via activemq.NewClient(&activemq.JmsConfig{BrokerUrl: ...}). Elastic APM imports (go.elastic.co/apm/...) are filtered out. |
| Database tables | SQL extracted from QueryString fields on model.PoolServiceRequest, pb.PoolExecuteQueryRequest, and in-house wrapper structs whose value flows into PoolExecuteQuery/Execute/ExecuteRaw. Schema-qualified names (<schema>.<table>) are preserved. MERGE INTO is classified as a write. When a SQL keyword cannot be matched but a table name is identifiable, the access mode is recorded as unknown. |
What gets excluded
- Vendor directories. Any
.gofile whose path contains a directory segment namedvendoris skipped end-to-end. The Go scanners reject vendored paths at the parser boundary, and the language-agnostic detectors are also handed a vendor-aware view so vendored Go source's embedded string literals cannot leak into SQL, URL, or SDK-pattern detections. - Build-constraint-gated files. A file with a non-trivial
//go:buildor// +buildline (e.g.linux && amd64,darwin,!cgo) is wholesale skipped because honoring the constraint requires a Go toolchain. Skipped files are surfaced indegraded_sectionsas"<section>: skipped <path> (build constraint requires toolchain)". - cgo files. A file containing
import "C"is skipped for the same reason and surfaced under the samedegraded_sectionsshape with reasoncgo directive requires toolchain. - Tokenization errors. Unterminated strings, raw strings, block comments, and invalid escape sequences are contained per file: the affected file contributes zero detections and is surfaced with reason
tokenization failed: <detail>. - DI and config noise.
fx.New/fx.Provide/fx.Invoke/fx.Module/fx.Hook/*fx.Lifecyclecalls describe internal wiring and never produce HTTP, scheduler, ActiveMQ, file-I/O, CLI, or external-service detections by themselves.viperconfiguration reads (*viper.Viper.GetString,SetConfigName,AddConfigPath, etc.) never contribute to the purpose summary or any detection.
Triggering Go analysis
No flag, no environment variable, no client opt-in. Run the server as documented above against a GitLab group containing Go projects — every refresh that fetches a snapshot with .go files or a go.mod automatically routes through the Go sub-analyzers. The result is observable through the same MCP tools and visualization routes as any other project:
get_project_purposereturns a summary derived fromgo.mod/ package doc when no README is present.get_project_iolists HTTP routes, cron schedules, ActiveMQ destinations, file I/O, and binary entry points found in the Go source.get_project_dependencieslistsfec_pool_service(when reached via eitherpbordbadapter), ActiveMQ brokers, and any URL-literal-derived service, plus the database tables extracted from in-process SQL.
When a Go file is skipped for any reason listed above, the affected section name appears in the profile's degraded_sections list with a structured "<section>: skipped <path> (<reason>)" entry so the operator can see exactly which files the analyzer could not reach.
Requirements
- Python 3.11+
- A GitLab access token with
read_apiandread_repositoryscopes for the target group.
Install
From this repository:
pip install -e ".[dev]"
The install registers a console script:
project-knowledge-mcp = project_knowledge_mcp.main:main
Configuration
Configuration is read from environment variables. On any missing or invalid value the server prints a single line to stderr that names the offending key and exits non-zero before either surface accepts traffic.
| Variable | Required | Default | Notes |
|---|---|---|---|
GITLAB_BASE_URL |
yes | — | e.g. https://gitlab.example.com. Must be http:// or https:// with a host. |
GITLAB_GROUP_PATH |
yes | — | Top-level group path (e.g. acme/platform) or numeric group id. |
GITLAB_ACCESS_TOKEN |
yes | — | Token with read_api + read_repository. |
GITLAB_VERIFY_SSL |
no | true |
Whether outbound HTTPS calls to GITLAB_BASE_URL validate the server's TLS certificate. Set to false (or 0/no/off) for a GitLab instance with a self-signed certificate; doing so removes MITM protection for those requests. |
ANALYSIS_BRANCH |
no | uat |
Branch fetched per project, regardless of GitLab default branch. |
REFRESH_INTERVAL |
no | none | When set (e.g. 15m, 1h), schedules a periodic full refresh and fires one immediately at startup so the visualization populates without waiting a full interval. Minimum 1 minute. When unset, no scheduler runs and no automatic refresh fires — trigger ingestion via the refresh_all_projects MCP tool. |
VISUALIZATION_PORT |
no | 7345 |
Loopback-only TCP port (127.0.0.1 and ::1). Range 1..65535. |
KNOWLEDGE_STORE_PATH |
no | data/knowledge_store.db |
SQLite database path. The parent directory is created if missing. |
LOG_LEVEL |
no | WARNING |
Root logging level for stderr output. The application's operationally important lines (visualization "ready" banner, refresh-progress per-project lines, refresh-complete summary) are emitted at WARNING so they surface under this default without dragging in third-party libraries' per-request chatter. Set LOG_LEVEL=INFO to additionally see less essential application INFO lines, or LOG_LEVEL=DEBUG for full diagnostic verbosity (including httpx/httpcore/uvicorn). Unknown values fall back to WARNING. |
The store persists across restarts: on reopen, readers immediately serve the last successfully committed snapshot, and an in-flight job aborted at shutdown does not affect that pointer.
Run
GITLAB_BASE_URL=https://gitlab.example.com \
GITLAB_GROUP_PATH=acme/platform \
GITLAB_ACCESS_TOKEN=glpat-... \
project-knowledge-mcp
On startup you will see a single log line:
Visualization_Server ready at http://127.0.0.1:7345
The MCP server simultaneously speaks JSON-RPC over stdin/stdout, so this command is normally launched by an MCP client rather than run interactively.
Use from an MCP client
Configure your MCP-aware tool (Claude Desktop, an editor MCP integration, etc.) to spawn the server. Example config:
{
"mcpServers": {
"project-knowledge": {
"command": "project-knowledge-mcp",
"env": {
"GITLAB_BASE_URL": "https://gitlab.example.com",
"GITLAB_GROUP_PATH": "acme/platform",
"GITLAB_ACCESS_TOKEN": "glpat-...",
"ANALYSIS_BRANCH": "uat",
"REFRESH_INTERVAL": "1h"
}
}
}
}
Available MCP tools
Eight tools are registered. The first six are read-only; the last two trigger ingestion.
| Tool | Arguments | Purpose |
|---|---|---|
list_projects |
— | List in-scope projects from the current Project_Catalog. |
get_project_purpose |
gitlab_project_id |
Purpose summary + reason. |
get_project_io |
gitlab_project_id |
Abstract inputs and outputs. |
get_project_dependencies |
gitlab_project_id |
External services + database tables. |
get_project_profile |
gitlab_project_id |
The complete Project_Profile. |
list_purpose_conflicts |
— | Pairs of projects with overlapping or contradictory primary responsibilities. |
refresh_all_projects |
— | Start a full refresh of every in-scope project. |
refresh_project |
gitlab_project_id |
Re-analyze a single project, copying every other profile from the parent snapshot. |
Refresh tools enforce a single-flight rule: while one ingestion job is running, additional refresh requests (from any source — tool call or scheduler) are rejected with the message Ingestion_Job is already in progress. The Knowledge_Store is unchanged.
Out-of-scope project ids return a tool result with isError: true and the message project {id} is not in scope.
Visualization
Browse http://127.0.0.1:7345/ (or your configured port). All routes serve text/html; charset=utf-8:
| Route | Shows |
|---|---|
/ |
Index of in-scope projects, sorted by id. Empty catalog → No Projects are in scope. No completed ingestion yet → no project knowledge available; run an Ingestion_Job. |
/projects/{project_id} |
Single project: purpose summary; abstract inputs grouped by category; abstract outputs grouped by category; external services labeled by kind; database tables labeled by access mode. Each empty section shows a section-specific empty-state message. |
/dependencies |
Mermaid graph: nodes = in-scope projects, edges = shared database tables. |
/static/<filename> |
Static file mount serving the package's static/ directory. Used by /dependencies to load cytoscape.min.js (primary) and mermaid.min.js (fallback). |
Enable graph rendering
The /dependencies page renders an interactive knowledge graph using Cytoscape.js. Drop the bundle at src/project_knowledge_mcp/static/cytoscape.min.js:
curl -L -o src/project_knowledge_mcp/static/cytoscape.min.js \
https://cdn.jsdelivr.net/npm/cytoscape@3/dist/cytoscape.min.js
When Cytoscape is loaded the page emits an interactive node-and-edge graph with zoom, pan, drag, and hover tooltips. The same diagram is also emitted as a Mermaid <pre class="mermaid"> block; when cytoscape.min.js is absent the page falls back to Mermaid by dropping a Mermaid bundle at src/project_knowledge_mcp/static/mermaid.min.js:
curl -L -o src/project_knowledge_mcp/static/mermaid.min.js \
https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js
For air-gapped networks, fetch either file from any machine that can reach the CDN and copy it into the same path. See src/project_knowledge_mcp/static/README.md for details. When both files are missing the pages still load — graphs degrade to the raw graph LR ... text.
Behavior pinned by the spec:
- Reads happen at request time — no in-memory caching of profiles.
- Non-matching paths → 404 with the requested path echoed in the body.
- Non-
GETmethods → 405 withAllow: GET. Knowledge_Storefailures → 503 with no profile-derived content.- Every successful response begins within 5 seconds.
Typical workflow
- Start the server with
REFRESH_INTERVAL=1h(or trigger refreshes manually). - After the first ingestion completes, the visualization populates and the MCP query tools start returning data.
- Iterate: as code in your GitLab projects evolves on
Analysis_Branch, each refresh produces a new snapshot. Readers atomically switch to the new snapshot only when the ingestion commits.
Operations
- Skipped projects. When a project has no
Analysis_Branch, the ingestion records a skip with reasonanalysis_branch_missingand continues with the rest. NoProject_Profileis produced for skipped projects; they still appear in the catalog so you can tell "in scope but not yet analyzed" from "out of scope". - Auth failures. A
401/403from GitLab aborts the in-flight job and surfaces the status code; the previous snapshot remains current. A404for the configured group raisesGitLabGroupNotFoundError(group_path). - Shutdown. SIGTERM/SIGINT triggers a documented shutdown ordering: stop accepting new HTTP connections → close MCP stdio → mark any in-flight snapshot
failed(thecurrent_snapshotpointer is unchanged) → flush and close the SQLite store → exit. - Port already in use. Startup prints
startup error: visualization.port {port} is already in useand exits non-zero.
Tests
pytest -m unit # ~3s
pytest -m property # Hypothesis, max_examples=100, ~30s
pytest -m integration # subprocess + HTTP + SQLite, ~15s
Or all of them:
pytest
Project layout
src/project_knowledge_mcp/
config.py # env loader + validator (single termination path)
errors.py # error type hierarchy
models.py # Project_Profile, EnumeratedProject, ConflictResult, ...
knowledge_store.py # SQLite + snapshot pointer + WAL
project_catalog.py # snapshot-scoped in-scope set
gitlab_connector.py # paginated GitLab API client
project_analyzer/ # purpose, io_extractor, external_services, db_tables
conflict_detector.py # classify_pair + find_all_conflicts
ingestion_coordinator.py # single-flight state machine
scheduler.py # periodic refresh
mcp_server.py # MCP over stdio + 8 tools
visualization_server.py # Starlette + Uvicorn (loopback only)
diagram_renderer.py # Jinja2 + inline Mermaid
main.py # wiring, startup, shutdown ordering
The full specification (requirements, design, and the 30 correctness properties the implementation is verified against) lives under .kiro/specs/project-knowledge-mcp/.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.