| name | performance-optimization |
| description | Node.js/TypeScript API performance, MCP protocol optimization, European Parliament API caching, async operations, and memory efficiency |
| license | MIT |
Performance Optimization Skill
Context
This skill applies when:
- Implementing MCP protocol handlers and tool endpoints
- Fetching data from European Parliament APIs
- Processing large datasets or document collections
- Designing caching strategies for API responses
- Optimizing database queries or data transformations
- Profiling and measuring performance bottlenecks
- Writing async/await code or Promise chains
- Handling concurrent requests or batch operations
- Reducing memory allocations and garbage collection pressure
- Optimizing startup time and initialization
Performance is critical for MCP servers as they power real-time AI interactions. Response times should be < 500ms, memory usage should be stable, and throughput should handle 100+ requests per 15-minute window.
Rules
- Measure Before Optimizing: Always profile and measure performance before making changes - no premature optimization
- Set Performance Budgets: API responses < 500ms, memory growth < 10MB per hour, startup time < 2s
- Cache Aggressively: Cache European Parliament API responses with appropriate TTL (5-60 minutes)
- Use Async/Await Properly: Avoid blocking the event loop - use async for I/O, sync for CPU-bound work
- Minimize Allocations: Reuse objects, use object pools, avoid creating unnecessary intermediate arrays
- Batch Operations: Group multiple API calls or database queries when possible
- Lazy Load: Defer loading of non-critical resources until needed
- Stream Large Data: Use Node.js streams for processing large documents or datasets
- Optimize Serialization: Minimize JSON parsing overhead, consider caching parsed results
- Index Data Structures: Use Maps/Sets for O(1) lookups instead of O(n) array searches
- Avoid Memory Leaks: Clear timers, remove event listeners, close connections properly
- Monitor Performance: Implement performance logging and alerting for regression detection
- Compress Responses: Use gzip/brotli for MCP responses when size exceeds 1KB
- Debounce/Throttle: Rate limit expensive operations like API calls or searches
- Use Worker Threads: Offload CPU-intensive work (parsing, transformation) to worker threads
Examples
✅ Good Pattern: Efficient European Parliament API Caching
import { LRUCache } from 'lru-cache';
interface CacheOptions {
ttl: number;
maxSize: number;
maxItems: number;
}
export class EuropeanParliamentCache {
private readonly searchCache: LRUCache<string, SearchResult>;
private readonly documentCache: LRUCache<string, Document>;
constructor() {
this.searchCache = new LRUCache<string, SearchResult>({
max: 1000,
maxSize: 50 * 1024 * 1024,
: .(value).,
: * * ,
: ,
: ,
});
. = <, >({
: ,
: * * ,
: .(value).,
: * * ,
: ,
});
}
(
: ,
: <>
): <> {
cacheKey = .(query);
cached = ..(cacheKey);
(cached) {
cached;
}
result = ();
..(cacheKey, result);
result;
}
(: ): {
normalized = {
: query..().(),
: query.?.(),
: query.,
: query.,
: query.,
};
.(normalized, .(normalized).());
}
(): {
..();
..();
}
() {
{
: {
: ..,
: ..,
: .. >
? (.. / ..) *
: ,
},
: {
: ..,
: ..,
},
};
}
}
✅ Good Pattern: Optimized MCP Tool Handler
export class OptimizedSearchHandler {
private readonly cache: EuropeanParliamentCache;
private readonly metrics: PerformanceMetrics;
private readonly inFlightRequests = new Map<string, Promise<SearchResult>>();
constructor(
cache: EuropeanParliamentCache,
metrics: PerformanceMetrics
) {
this.cache = cache;
this.metrics = metrics;
}
async handleSearch(request: ToolRequest): Promise<ToolResponse> {
const startTime = performance.now();
try {
const query = .(request.);
requestKey = .(query);
existingRequest = ..(requestKey);
(existingRequest) {
..();
existingRequest;
}
requestPromise = .(query);
..(requestKey, requestPromise);
{
result = requestPromise;
duration = performance.() - startTime;
..(, duration, );
{
: [{
: ,
: .(result, , ),
}],
};
} {
..(requestKey);
}
} (error) {
duration = performance.() - startTime;
..(, duration, );
error;
}
}
(: ): <> {
..(query, () => {
europeanParliamentApi.(query);
});
}
(: ): {
;
}
(: ): {
( params !== || params === ) {
();
}
query = params <, >;
{
: .(query., , , ),
: query.
? .(query., , , )
: ,
: query.
? .(query.)
: ,
: query.
? .(query.)
: ,
: .(query., , , ) ?? ,
};
}
(
: ,
: ,
: ,
:
): {
( value !== ) {
();
}
trimmed = value.();
(trimmed. < minLength || trimmed. > maxLength) {
(
);
}
trimmed;
}
(
: ,
: ,
: ,
:
): | {
(value === || value === ) {
;
}
num = (value);
(!.(num) || num < min || num > max) {
(
);
}
num;
}
(: ): {
( value !== ) {
();
}
dateRegex = ;
(!dateRegex.(value)) {
();
}
value;
}
}
✅ Good Pattern: Async Batch Processing
export async function fetchDocumentsBatch(
documentIds: string[],
options: { concurrency?: number; timeout?: number } = {}
): Promise<Map<string, Document>> {
const concurrency = options.concurrency ?? 10;
const timeout = options.timeout ?? 30000;
const results = new Map<string, Document>();
const errors: Array<{ id: string; error: Error }> = [];
for (let i = 0; i < documentIds.length; i += concurrency) {
const batch = documentIds.slice(i, i + concurrency);
const batchResults = await Promise.(
batch.( (id) => {
timeoutPromise = <>(
( ( ()), timeout)
);
fetchPromise = europeanParliamentApi.(id);
.([fetchPromise, timeoutPromise]);
})
);
batchResults.( {
id = batch[index];
(result. === ) {
results.(id, result.);
} {
errors.({ id, : result. });
}
});
}
(errors. > ) {
.(, errors);
}
results;
}
✅ Good Pattern: Memory-Efficient Stream Processing
import { pipeline } from 'stream/promises';
import { Transform } from 'stream';
export async function processLargeDocument(
documentId: string,
outputPath: string
): Promise<void> {
const inputStream = await europeanParliamentApi.getDocumentStream(documentId);
const textExtractor = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
try {
const text = extractTextFromChunk(chunk);
callback(null, text);
} catch (error) {
callback(error as Error);
}
},
});
const sanitizer = ({
: ,
() {
{
sanitized = (chunk.());
(, sanitized);
} (error) {
(error );
}
},
});
outputStream = fs.(outputPath);
(
inputStream,
textExtractor,
sanitizer,
outputStream
);
}
✅ Good Pattern: Performance Monitoring
export class PerformanceMetrics {
private readonly latencies: number[] = [];
private readonly errors = new Map<string, number>();
private coalescedRequests = 0;
private totalRequests = 0;
recordRequest(tool: string, duration: number, success: boolean): void {
this.totalRequests++;
if (success) {
this.latencies.push(duration);
if (this.latencies.length > 1000) {
this.latencies.shift();
}
} else {
this.errors.set(tool, (..(tool) ?? ) + );
}
(duration > ) {
.();
}
}
(): {
.++;
}
() {
(.. === ) {
;
}
sorted = [....].( a - b);
{
: {
: .,
: .,
: (. / .) * ,
},
: {
: sorted[.(sorted. * )],
: sorted[.(sorted. * )],
: sorted[.(sorted. * )],
: sorted.( a + b, ) / sorted.,
: sorted[],
: sorted[sorted. - ],
},
: .(.),
: {
: process.(). / / ,
: process.(). / / ,
: process.(). / / ,
},
};
}
(): {
lastCheck = .();
( {
now = .();
lag = now - lastCheck - ;
(lag > ) {
.();
}
lastCheck = now;
}, );
}
}
❌ Bad Pattern: Synchronous API Calls Blocking Event Loop
export function searchDocumentsSync(query: string): Results {
const response = syncHttpGet(`/api/search?q=${query}`);
const data = JSON.parse(response);
return processResults(data);
}
export async function fetchMultipleDocuments(ids: string[]): Promise<Document[]> {
const results: Document[] = [];
for (const id of ids) {
const doc = await europeanParliamentApi.getDocument(id);
results.push(doc);
}
return results;
}
❌ Bad Pattern: No Caching, Repeated API Calls
export async function handleSearchRequest(query: SearchQuery): Promise<Results> {
return await europeanParliamentApi.search(query);
}
const cache = new Map();
export async function getCachedDocument(id: string): Promise<Document> {
if (cache.has(id)) {
return cache.get(id);
}
const doc = await europeanParliamentApi.getDocument(id);
cache.set(id, doc);
return doc;
}
❌ Bad Pattern: Loading Large Data Into Memory
export async function processDocument(id: string): Promise<string> {
const document = await europeanParliamentApi.getFullDocument(id);
const text = extractText(document);
const processed = processText(text);
return processed;
}
export function transformDocuments(docs: Document[]): ProcessedDocument[] {
const filtered = docs.filter(d => d.type === 'REPORT');
const mapped = filtered.map(d => ({ ...d, processed: true }));
const sorted = mapped.sort((a, b) => a..(b.));
sorted;
}
❌ Bad Pattern: No Performance Monitoring
export async function handleToolRequest(request: ToolRequest): Promise<ToolResponse> {
try {
return await processRequest(request);
} catch (error) {
return { error: error.message };
}
}
export async function search(query: string): Promise<Results> {
console.log('Searching...');
const results = await europeanParliamentApi.search(query);
console.log('Done');
return results;
}
References
Performance Tools
Caching
Best Practices
ISMS Policies
Primary:
Supporting:
Remember
- Measure first: Profile before optimizing - data beats intuition
- Set budgets: Target < 500ms response time, < 10MB memory growth per hour
- Cache aggressively: European Parliament data changes infrequently
- Use async properly: Never block the event loop with synchronous I/O
- Batch operations: Parallel execution reduces total latency
- Stream large data: Constant memory usage for large documents
- Monitor continuously: Track latency, cache hit rate, error rate, memory
- Optimize the hot path: Focus on the 20% of code handling 80% of requests
- Avoid premature optimization: Write clear code first, optimize when measured slow
- Consider caching layers: In-memory (LRU) → Redis → API
- Test under load: Use benchmarks to verify optimizations work
- Memory leaks: Clear timers, remove listeners, close connections
- European Parliament API: Respect rate limits, cache extensively
- MCP protocol: Minimize serialization overhead in tool responses
- Balance tradeoffs: Performance vs maintainability vs memory usage