MCP server: migrating from 0.6.x to 0.9.x¶
This page is for operators upgrading custom Python MCP servers
written against pre-0.9 kglite (typically 0.6.x – 0.8.x) to the
bundled kglite-mcp-server shipped from 0.9.20 onwards. The arc is
large — 0.6 to 0.9 covers a full re-platforming of the MCP surface
— so this guide covers the differences as a single jump rather than
release-by-release.
If you’re on 0.9.19 or later and want the smaller hops, see MCP server: pre-0.9.20 migrations.
Note
Historical. As of 0.10.25 the MCP server is the standalone pure-Rust
kglite-mcp-server binary (cargo install kglite-mcp-server); the
bundled Python entry point this page targets has been retired. New
setups should follow MCP Servers.
High-level shift¶
Before (0.6.x – 0.8.x): each MCP server was a hand-written
Python script using the mcp SDK directly. The server registered
tools like cypher_query, graph_overview, read_source,
grep_source, etc., wired them to a KnowledgeGraph instance, and
ran its own watcher / embedder / CSV-export logic. Operators
typically maintained five-plus custom Python servers, each ~500–
1000 LOC.
After (0.9.20+): one bundled kglite-mcp-server binary (a Python
entry point since 0.9.20) is driven entirely by a YAML manifest. The
operator’s custom Python is gone; configuration lives in the
manifest. Five custom Python servers retired across the 0.9.16 →
0.9.27 arc.
Install¶
pip install --upgrade kglite
The default install pulls everything the server needs: mcp,
pyyaml, aiohttp, and watchdog. (Historical: this era used an
[embed] extra for fastembed; it was removed in 0.10.28 —
embedding is now bring-your-own, pip install whatever library you
name in extensions.embedder.library.) There is no [code-tree]
extra — tree-sitter grammars are bundled into the Rust extension and
load with kglite itself.
After install, kglite-mcp-server is on PATH.
Translation cheat-sheet¶
If you’re staring at a custom Python MCP server and trying to pick the equivalent flag, this maps the most common patterns:
Old pattern in your custom script |
New mode |
|---|---|
Loads a |
|
Loads a disk-backed graph directory ( |
|
Clones GitHub repos at runtime and builds code-trees |
|
Uses |
|
Watches a single fixed directory and rebuilds on change |
|
Exposes only |
|
Reads |
|
Spawns its own CSV-over-HTTP listener |
|
Wires a custom embedder for |
|
A complete custom server typically maps onto one mode + a manifest
with one or two extensions: entries. The “extra Python” you used
to hand-write — listener setup, embedder wiring, watcher debounce,
CSV write paths — all moves into manifest declarations.
.kgl format compatibility¶
.kgl files written by 0.6.x – 0.8.x cannot be loaded by 0.9.x.
The serialisation format changed across the storage-backend
refactor (DirGraph traits, disk-backed mode, mapped-mode columnar
layout) and graphs need to be rebuilt under 0.9.x. The error you
get loading an old file is explicit (“unsupported format version”
or similar) rather than silent corruption — that part works as
intended.
If your build pipeline lives in your own scripts, re-running them
under the new kglite produces a fresh 0.9.x-compatible .kgl. If
you don’t have the build pipeline anymore, fluent-API or Cypher
CREATE against an empty KnowledgeGraph() is the supported way
to rebuild from source data — see the
Graph construction guide.
Five operating modes¶
The bundled server picks its mode from CLI flags (and workspace.kind
when a manifest is present):
Flag |
Mode kind |
What it does |
|---|---|---|
|
|
Static graph from a |
|
|
Github-clone-tracker: agent calls |
|
|
Fixed local source root with sibling-swap via |
|
|
Single directory, auto-rebuild on file changes. Like |
|
|
Source-tools-only binding (read-only |
Manifest auto-detection: if --graph X.kgl is passed and
X_mcp.yaml sits next to it, the manifest loads automatically. For
--workspace DIR, the server looks for workspace_mcp.yaml first
inside the directory, then (since 0.9.29 / mcp-methods 0.3.33) one
level up — but only when the parent manifest opts in via
workspace.applies_to. This lets operators use either of these
layouts:
# Layout 1: manifest inside the workspace dir (no opt-in needed)
workspace_dir/
├── workspace_mcp.yaml
└── repos/ # clones land here
# Layout 2: manifest as a sibling (since 0.9.29)
my_project/
├── workspace_mcp.yaml # declares `workspace.applies_to: ./*`
└── repos/ # --workspace points here
For layout 2, the parent manifest must declare which child directory (or directories) it covers:
workspace:
kind: github
applies_to: ./* # any direct child (most permissive)
# or:
applies_to: ./repos # literal: only "repos" matches
# or:
applies_to: ./prod-* # glob: matches "prod-api", "prod-web", etc.
# or:
applies_to: [./repos, ./clones] # list form
Without the applies_to opt-in, the parent-walk discovery is
refused — a deliberate safety property to prevent silent-wrong-
manifest if the operator points --workspace at any unrelated
sibling directory under a workspace-manifest parent. The framework
emits a tracing log when it considers and rejects a parent-walk
match (visible if you set RUST_LOG=mcp_methods=debug).
Pattern syntax follows the globset crate (*, ?, [abc]).
Multi-segment paths and .. are rejected at manifest parse time
with explicit error messages.
Tool surface differences from pre-0.9.x¶
Pre-0.9.x tool name |
0.9.20+ tool name(s) |
Notes |
|---|---|---|
|
|
Split into two tools. |
|
|
Renamed; arguments unchanged ( |
|
|
Same name, same arguments. |
|
|
|
|
|
Same name. Now supports |
|
|
Same name. |
|
|
Off by default — operators must opt in via manifest. Pre-0.9.x always exposed it. |
|
|
Same surface: name / delete / update / force_rebuild. The bundled version uses the validated mcp-methods Rust path (clone-and-track, inventory, atomic root swap). |
|
|
Same surface. Sibling swap under the workspace root sandbox. |
(custom github wrapper) |
|
Built-in; auto-registers when |
Embedder transition¶
Pre-0.9.x custom servers typically loaded sentence-transformers
directly, with torch + MPS for GPU acceleration on Apple Silicon:
# Pre-0.9.x pattern
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-m3", device="mps")
class CustomEmbedder:
def embed(self, texts: list[str]) -> list[list[float]]:
return model.encode(texts).tolist()
0.9.20+ uses fastembed (ONNX runtime) as the default backend:
# 0.9.20+ pattern — declared in the manifest
extensions:
embedder:
backend: fastembed
model: BAAI/bge-m3
cooldown: 1200 # idle-evict after 20 min, reload on next use
The cache directory is ~/.cache/fastembed/ (same as pre-0.9.x
direct fastembed installs). First call downloads the ONNX weights;
subsequent calls reuse the cached copy.
Performance note: fastembed’s ONNX runtime is CPU-bound on
default install (no coreml/mps execution provider). On Apple
Silicon this is a measurable change from torch+MPS in your custom
server — bge-m3 inference goes from ~50 ms/query (MPS) to ~200–400
ms/query (CPU). If query-time embedding is on your hot path, decide
whether to:
Accept the CPU latency (simpler ops; reasonable for low-volume exploration servers).
Pre-compute embeddings via
g.embed_texts("Type", "prop")and store them on the graph — thentext_scoreis a vector lookup, not an inference call.Keep your custom embedder. Pass a Python class instance implementing the embedder duck-type through
PyEmbedderAdapterwhen constructing the server programmatically. (Not currently wired into the manifest schema — file an issue if you need it surfaced via YAML.)
For most operators, option 2 is the right answer: pre-compute via
g.embed_texts(...) at graph-build time, save with g.save(...),
and text_score at query time is microseconds rather than hundreds
of milliseconds.
Manifest cheat-sheet¶
--graph mode (static graph)¶
prospect_mcp.yaml next to prospect.kgl:
name: Prospect
instructions: |
Code intelligence for the prospect project. Use cypher_query first
to locate entities, then read_code_source(qualified_name=...) for
the slice.
overview_prefix: |
## Two read paths
- read_code_source for graph-indexed symbols.
- read_source for files without graph nodes.
--workspace (github clone-tracker)¶
workspace_mcp.yaml inside the workspace directory. Full example:
examples/open_source_workspace_mcp.yaml.
workspace.kind: local¶
name: Code Review
workspace:
kind: local
root: /Volumes/.../Koding # sandbox boundary
watch: true # auto-rebuild on file changes
--watch (single dir)¶
No manifest required for the simplest case:
kglite-mcp-server --watch /path/to/project
Pair with a sibling project_mcp.yaml for custom instructions /
cypher tools / extensions.
Tool overrides (0.9.27+)¶
The tools[].bundled: block (added in 0.9.27 / mcp-methods 0.3.31)
lets you customise the agent-facing surface of bundled tools without
declaring them inline. Useful for replacing the boilerplate
guidance pre-0.9.x servers carried in the global instructions: blob:
tools:
- bundled: repo_management
description: |
FIRST STEP: call repo_management('org/repo') to clone.
... (the call patterns you used to teach in instructions)
- bundled: ping
hidden: true # drop from tools/list + reject calls
The 12 bundled tools: cypher_query, graph_overview, ping,
read_code_source, save_graph, read_source, grep,
list_source, repo_management, set_root_dir, github_issues,
github_api. Unknown names fail at boot with the valid catalogue
listed in stderr.
Common migration gotchas¶
workspacemode didn’t build graphs before 0.9.28. Pre-0.9.28repo_management('org/repo')would clone the repo but the workspace’spost_activatehook was never wired inserver.py, so no code-tree graph was built. Fixed in 0.9.28: the hook now fires on every activate and rebuilds the graph + rebindssource_roots. If you tried--workspacebefore 0.9.28 and gave up, retry with 0.9.28+.kglite.code_treeattribute access was broken before 0.9.28 in the mcp_server path —kglite/__init__.pydoesn’t import the submodule at module load. The fix is to usefrom kglite import code_treeinstead ofkglite.code_tree.X(the mcp_server’sbuild_code_treedoes this now). External callers writing custom embedders or graph builders should follow the same pattern.save_graphis opt-in. Pre-0.9.x servers always exposed it; the bundled server gates onbuiltins.save_graph: true. Without that flag the tool doesn’t register at all.Manifest paths are manifest-relative.
env_file: ../.envwalks one directory up from the manifest, NOT from the CWD where the server was launched. This bit operators expecting CWD-relative resolution.Auto-detected manifests.
kglite-mcp-server --graph X.kgllooks forX_mcp.yamlnext to the .kgl.kglite-mcp-server --workspace DIRlooks forworkspace_mcp.yamlinside the workspace dir. If you’d prefer not to auto-load, pass--no-config(always wins over auto-detection).
Reference¶
MCP Servers — end-to-end setup guide for new servers (post-migration default).
Examples: workspace mode (local + github-clone-tracker) — workspace + watch mode walkthroughs with the operator-modelled
open_source_workspace_mcp.yaml.CHANGELOG.md — full release-by-release notes if you need to verify exactly which version introduced a specific change.