Graph slicing is how SDL-MCP answers the question: “which symbols are relevant to this task?” Instead of reading files in the same directory, it follows the dependency graph. Starting from the symbols most relevant to a task, it traverses call and import edges outward, scores each candidate by relevance, and returns the N most important symbols within a token budget.
Why Directories Are the Wrong Abstraction
Code doesn’t respect directory boundaries. A function in src/auth/validate.ts might depend on src/db/queries.ts, src/config/types.ts, and src/util/hashing.ts. Directory-based context misses all of these cross-cutting relationships and floods the window with irrelevant sibling files instead.
A graph slice follows the actual connections.
How Slicing Works
Your Task: "Fix the auth middleware"
│
▼
┌──────────────┐
│ Entry Symbols │ ← auto-discovered from taskText
│ authenticate │ or explicitly provided
│ validateToken │
└──────┬───────┘
│
BFS / beam search
across weighted edges
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────────┐ ┌─────────┐
│hashPw│ │getUserById│ │JwtConfig│ ← call weight: 1.0
└──┬───┘ └────┬─────┘ └────┬────┘ import weight: 0.6
│ │ │ config weight: 0.8
▼ ▼ ▼
┌──────┐ ┌────────┐ ┌──────────┐
│bcrypt│ │dbQuery │ │envLoader │ ← frontier (just
└──────┘ └────────┘ └──────────┘ outside the slice)
═══════════════════════════════════════
Token budget reached.
8 cards returned (~800 tokens)
vs. reading 8 files (~16,000 tokens)
Edge Weights
Not all relationships indicate equal relevance. SDL-MCP weights edges by how strongly they signal that the connected symbol matters to the task:
| Edge Type | Weight | Rationale |
|---|
| Call | 1.0 | If A calls B, B is almost certainly relevant to understanding A |
| Config | 0.8 | Configuration dependencies are important but less direct |
| Import | 0.6 | An import indicates awareness, not necessarily deep relevance |
Symbol Scoring
Each symbol in the BFS frontier is scored by a weighted combination of signals:
| Signal | Weight | Description |
|---|
| Query relevance | 0.40 | Search term overlap from taskText |
| Stack trace locality | 0.20 | Proximity to parsed stack trace entries |
| Hotness | 0.15 | Composite of fan-in, fan-out, and churn metrics |
| Structure | 0.15 | File path structural specificity |
| Kind | 0.10 | Symbol kind (functions/methods scored higher than variables) |
| Cluster cohesion | +0.15 boost | Same-cluster symbols; +0.05 for related clusters |
Low-confidence call edges can be filtered via minConfidence or minCallConfidence to keep the slice tight and prevent noise from ambiguous resolutions.
Token Efficiency
| Approach | Symbols Covered | Token Cost |
|---|
| Reading 8 files directly | 8 files | ~16,000 tokens |
| Graph slice (8 cards) | 8 symbols + graph structure | ~800 tokens |
The slice returns the most relevant symbols — not arbitrary file contents — at 20x lower token cost.
Auto-Discovery Mode
You don’t need to know symbol IDs. Pass a taskText string and SDL-MCP automatically discovers the best entry symbols:
{
"repoId": "my-app",
"taskText": "fix the authentication timeout bug",
"budget": { "maxCards": 30, "maxEstimatedTokens": 4000 },
"includeRetrievalEvidence": true
}
SDL-MCP supports two discovery paths depending on index health:
Hybrid retrieval (when semantic.retrieval.mode: "hybrid" and indexes are healthy):
- Run a single hybrid search combining full-text search (FTS), vector similarity, and Reciprocal Rank Fusion (RRF)
- Score and rank candidates across all retrieval sources
- Build the slice from the top-ranked seeds
Legacy fallback (when hybrid is unavailable):
- Token-by-token
searchSymbolsLite fan-out across the task text
- Score and rank individual matches
- Build the slice from the top-ranked seeds
The hybrid path produces better seed quality because it understands meaning via vector search rather than just token overlap, and it runs a single efficient query instead of per-token fan-out.
When includeRetrievalEvidence: true is set, the slice response explains how seeds were discovered:
{
"retrievalEvidence": {
"mode": "hybrid",
"symptomType": "taskText",
"candidateCountPerSource": {
"fts": 28,
"vector:all-MiniLM-L6-v2": 24
},
"fusionLatencyMs": 8,
"fallbackReason": null
}
}
The symptomType field classifies the input: "taskText", "stackTrace", "failingTest", or "editedFiles".
You can also pass stackTrace, failingTestPath, or editedFiles instead of taskText to seed the slice from different kinds of context signals.
Slice Lifecycle
Slices aren’t one-shot. They have a full lifecycle with handles, leases, and delta updates:
slice.build slice.refresh slice.spillover.get
──────────► handle ──────────► delta ──────────► overflow
│ │ │
│ lease expires │ nothing changed │ no more pages
▼ ▼ ▼
rebuild notModified done
Build the slice
sdl.slice.build creates the slice, traverses the dependency graph, scores candidates, and returns the top symbols within budget. The response includes a handle (opaque reference) and a lease (expiry time).{
"tool": "sdl.slice.build",
"arguments": {
"repoId": "my-app",
"taskText": "fix the authentication timeout bug",
"budget": { "maxCards": 30, "maxEstimatedTokens": 4000 }
}
}
Refresh the slice (delta-only)
sdl.slice.refresh returns only what changed since your last version — dramatically cheaper than rebuilding. If nothing changed, the response is { "notModified": true }.{
"tool": "sdl.slice.refresh",
"arguments": {
"repoId": "my-app",
"handle": "<handle from build>",
"knownCardEtags": { "validateToken": "a7f3c2..." }
}
}
Page through spillover
sdl.slice.spillover.get pages through symbols that scored well but didn’t fit within the budget. Call it repeatedly until hasMore is false.{
"tool": "sdl.slice.spillover.get",
"arguments": {
"repoId": "my-app",
"handle": "<handle from build>",
"page": 1
}
}
Slices support three compact wire format versions for minimal bandwidth:
| Version | Encoding | Best For |
|---|
| V1 | Shortened field names | Backward compatibility |
| V2 (default) | + deduplicated file paths and edge type lookup tables | Most use cases |
| V3 | + grouped edge encoding | Large, edge-dense graphs |
Combined with ETag-based conditional cards (knownCardEtags), a slice refresh can return zero duplicate data — only the symbols that actually changed.
Wire format version is negotiated automatically. You don’t need to configure it unless you are integrating with a custom MCP client that requires a specific version.