| name | serena-code-reading |
| description | Token-efficient code reading protocol using Serena's progressive disclosure pattern (90-95% token savings). Provides mandatory workflow, tool reference, and common patterns for symbol-level code navigation. |
Serena Code Reading Protocol
Purpose: Achieve 90-95% token savings by using Serena's progressive disclosure instead of reading entire files.
Mandatory Pattern (93% Token Savings)
ALWAYS use progressive disclosure instead of reading entire files:
1. Overview First (~200 tokens)
mcp__serena__get_symbols_overview({
relative_path: "src/services/auth.service.ts",
});
Returns: File structure showing all top-level symbols (classes, functions, interfaces) with their signatures.
Use when: You need to understand what's in a file before diving into specific code.
2. Check Signatures (~50 tokens per symbol)
mcp__serena__find_symbol({
name_path_pattern: "AuthService/login",
relative_path: "src/services/auth.service.ts",
include_body: false, // Just the signature
depth: 0,
});
Returns: Method/function signature without implementation details.
Use when: You need to understand the interface (parameters, return type) but not the implementation.
3. Read Bodies Selectively (~100 tokens per symbol)
mcp__serena__find_symbol({
name_path_pattern: "AuthService/login",
relative_path: "src/services/auth.service.ts",
include_body: true, // Full implementation
depth: 0,
});
Returns: Complete symbol definition including implementation.
Use when: You actually need to read/edit the implementation.
Token Impact Comparison
❌ Traditional Approach (Read entire file):
Read file (2,000 lines) = ~5,000 tokens
✅ Serena Progressive Disclosure:
get_symbols_overview → ~200 tokens (file structure)
find_symbol (no body) → ~50 tokens (signature)
find_symbol (with body) → ~100 tokens (implementation)
─────────────────────────────────────────
Total: ~350 tokens
SAVINGS: 93% (4,650 tokens saved)
Real-world impact:
- Exploring 10 files: 50,000 tokens → 3,500 tokens (93% savings)
- Editing 5 symbols: 25,000 tokens → 500 tokens (98% savings)
Core Tools Reference
File Structure
get_symbols_overview- Get file outline with all top-level symbolslist_dir- List directory contentsfind_file- Find files by pattern
Symbol Navigation
find_symbol- Search for symbols by name pathfind_referencing_symbols- Find all references to a symbolsearch_for_pattern- Regex search across codebase
Precise Editing
replace_symbol_body- Replace symbol implementation without reading full fileinsert_after_symbol- Add content after symbolinsert_before_symbol- Add content before symbolrename_symbol- Rename with cross-codebase updates
Common Workflows
Workflow 1: Understanding a New File
// Step 1: Get overview
const overview = await mcp__serena__get_symbols_overview({
relative_path: "src/services/payment.service.ts",
});
// Step 2: Check interesting symbol signatures
const processPaymentSignature = await mcp__serena__find_symbol({
name_path_pattern: "PaymentService/processPayment",
include_body: false, // Just signature
});
// Step 3: Read only what you need
const processPaymentBody = await mcp__serena__find_symbol({
name_path_pattern: "PaymentService/processPayment",
include_body: true, // Full implementation
});
// Result: ~350 tokens vs 5,000 tokens (93% savings)
Workflow 2: Finding Where Something Is Used
// Find all references to a function
const references = await mcp__serena__find_referencing_symbols({
name_path: "validateToken",
relative_path: "src/utils/auth.utils.ts",
});
// Returns: List of all places that call validateToken
// With code snippets showing context
// ~500 tokens vs 20,000 tokens (97.5% savings)
Workflow 3: Editing a Specific Method
// No need to read the entire file!
// Just replace the symbol body directly
await mcp__serena__replace_symbol_body({
name_path: "AuthService/login",
relative_path: "src/services/auth.service.ts",
body: `async login(email: string, password: string): Promise<User> {
// New implementation
const user = await this.userRepository.findByEmail(email);
if (!user) throw new UnauthorizedException();
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) throw new UnauthorizedException();
return user;
}`,
});
// Result: ~100 tokens vs 5,000 tokens (98% savings)
Workflow 4: Adding a New Method to a Class
// Insert after existing method without reading full file
await mcp__serena__insert_after_symbol({
name_path: "AuthService/login",
relative_path: "src/services/auth.service.ts",
body: `
async logout(userId: string): Promise<void> {
await this.sessionRepository.deleteByUserId(userId);
this.logger.info('User logged out', { userId });
}`,
});
// Result: ~100 tokens vs 5,000 tokens (98% savings)
Name Path Syntax
Serena uses "name paths" to identify symbols (like file paths for code):
Basic Patterns
// Find any symbol with this name
"UserService"; // Matches: class UserService, interface UserService, etc.
// Find method inside class
"UserService/createUser"; // Matches: createUser method in UserService
// Find nested class
"AuthModule/AuthService"; // Matches: AuthService inside AuthModule
// Absolute path (exact match from file root)
"/UserService/createUser"; // Must be at file root level
TypeScript/JavaScript Examples
// File: auth.service.ts
export class AuthService {
async login() { ... } // Name path: "AuthService/login"
async logout() { ... } // Name path: "AuthService/logout"
private validateToken() { ... } // Name path: "AuthService/validateToken"
}
export function hashPassword() { ... } // Name path: "hashPassword"
Advanced Patterns
// Substring matching (find all methods starting with "get")
find_symbol({
name_path_pattern: "UserService/get",
substring_matching: true,
});
// Matches: getUserById, getUserByEmail, getUserProfile
// Search by symbol kind (only classes)
find_symbol({
name_path_pattern: "Service",
include_kinds: [5], // 5 = Class
});
// Exclude certain kinds
find_symbol({
name_path_pattern: "User",
exclude_kinds: [13], // 13 = Variable
});
Symbol Kinds Reference
// Common LSP symbol kinds:
1 = File;
2 = Module;
3 = Namespace;
4 = Package;
5 = Class;
6 = Method;
7 = Property;
8 = Field;
9 = Constructor;
10 = Enum;
11 = Interface;
12 = Function;
13 = Variable;
14 = Constant;
Best Practices
✅ DO
Always start with overview
get_symbols_overview(file); // See what's there firstCheck signatures before reading bodies
find_symbol(name, (include_body = false)); // Interface onlyRead only what you need
find_symbol(specific_symbol, (include_body = true)); // One symbol at a timeUse find_referencing_symbols for impact analysis
find_referencing_symbols(symbol); // Before refactoringEdit at symbol level when possible
replace_symbol_body(); // Precise changes
✅ DO INSTEAD
Use Serena tools for all code exploration
✅ get_symbols_overview("src/services/huge-file.ts") // ~200 tokens ❌ Read("src/services/huge-file.ts") // 5,000 tokens wastedUse include_body=false for exploration; true only for implementation
✅ get_symbols_overview() // Efficient overview ✅ find_symbol("*", include_body=false) // Signature only ❌ find_symbol("*", include_body=true) // WastefulALWAYS check overview first before guessing symbol names
✅ get_symbols_overview() → see actual names → find_symbol() ❌ find_symbol("createUser") // Might not exist
When to Use Traditional Read Tool
Rare cases where reading entire file is acceptable:
Very small files (< 50 lines)
- Config files:
.env.example,.nvmrc - Simple utilities with 1-2 functions
- Config files:
Non-code files
- Documentation:
README.md,CHANGELOG.md - Data files:
package.json,tsconfig.json
- Documentation:
When you need full context
- Understanding complex interdependencies within a single file
- After using Serena tools and still need more context
Rule of thumb: If a file has symbols (classes, functions), use Serena. Otherwise, Read is fine.
Troubleshooting
Symbol Not Found
// Error: Symbol "UserService/createUser" not found
// Solutions:
1. Check spelling: get_symbols_overview(file) to see actual names
2. Use substring matching: find_symbol("create", substring_matching=true)
3. Search pattern: search_for_pattern("createUser")
Multiple Matches
// Multiple symbols match "User"
// Solutions:
1. Use more specific path: "UserService/User" instead of "User"
2. Use absolute path: "/UserService/User"
3. Filter by kind: include_kinds=[5] for classes only
Need to Find Symbol Location
// Don't know which file has the symbol
// Solutions:
1. Omit relative_path: find_symbol("UserService") // Searches entire codebase
2. Restrict to directory: relative_path="src/services"
3. Use pattern search: search_for_pattern("class UserService")
Integration with Other MCPs
Serena + Context7 (Library Documentation)
// 1. Get library docs
const docs = await mcp__Context7__get_library_docs({
context7CompatibleLibraryID: "/prisma/prisma",
});
// 2. Find where it's used in your code
const prismaUsage = await mcp__serena__search_for_pattern({
substring_pattern: "PrismaClient",
});
// 3. Understand implementation
const schema = await mcp__serena__get_symbols_overview({
relative_path: "packages/database/schema.prisma",
});
Serena + Memories (Pattern Storage)
// 1. Check for prior patterns in Serena memories
const memories = await list_memories();
const authPatterns = memories.filter(
(m) => m.includes("auth") || m.includes("pattern"),
);
for (const pattern of authPatterns) {
const content = await read_memory({ memory_file_name: pattern });
}
// 2. Find existing auth code
const authService = await mcp__serena__find_symbol({
name_path_pattern: "AuthService",
include_body: true,
});
// 3. Store new pattern in memory
write_memory({
memory_file_name: "pattern-jwt-auth.md",
content: `# Pattern: JWT Auth with Refresh Tokens
## Implementation
- Access token: 15min TTL
- Refresh token: 7 days in httpOnly cookie
- Refresh rotation on each use
## Tags
auth, jwt, security`,
});
Summary
Remember: Progressive Disclosure
- Overview → See structure
- Signatures → Understand interfaces
- Bodies → Read only what's needed
Token Savings: 90-95%
- 10 files explored: 50,000 → 3,500 tokens
- 5 symbols edited: 25,000 → 500 tokens
When in doubt: Start with get_symbols_overview and work your way down to specific symbols.