ansible-automation-eng…
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Async/await, concurrency control, and error propagation patterns for MCP documentation server performance
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Async/await, concurrency control, and error propagation patterns for MCP documentation server performance
description: Async/await, concurrency control, and error propagation patterns for MCP documentation server performance
> **Scope**: MCP server file I/O concurrency, indexing performance, error propagation. Node.js 18+, TypeScript 5.0+.
| Pattern | Version | Use When | Avoid When | |---------|---------|----------|------------| | `Promise.all(batch)` | Node 18+ | Bounded concurrent I/O | Unbounded arrays (OOM risk) | | `Promise.allSettled()` | Node 12.9+ | Must collect all results despite failures | Need fast-fail on first error | | `Promise.race()` | Node 18+ | Startup timeout racing | Error handling (loses other results) | | `async/await` in handlers | Always | All `setRequestHandler` callbacks | Sync callbacks (blocks event loop) | | `AbortController` | Node 18+ | Cancellable indexing operations | Simple timeouts (use setTimeout) |
---
Fixed-size batches to avoid exhausting file descriptors.
async function indexFilesInBatches(
files: string[],
batchSize: number,
processFile: (path: string) => Promise<void>
): Promise<{ processed: number; failed: number }> {
let processed = 0;
let failed = 0;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
const results = await Promise.allSettled(batch.map(processFile));
for (const result of results) {
if (result.status === 'fulfilled') {
processed++;
} else {
failed++;
console.warn(`[warn] indexing failed: ${result.reason}`);
}
}
}
return { processed, failed };
}
// Usage: process 50 files at a time
await indexFilesInBatches(allFiles, 50, (f) => this.parseAndCache(f));**Why**: `Promise.all(files.map(...))` on 10,000 files opens 10,000 file handles simultaneously. Most OS defaults limit to 1024 open file descriptors. `EMFILE: too many open files` crashes the indexer.
---
Serve before indexing completes.
class DocsServer {
private indexingComplete = false;
async start(): Promise<void> {
const STARTUP_TIMEOUT_MS = 15_000;
const indexingDone = this.runIndexing();
// Serve partial results after timeout — don't block connection
const startupFence = Promise.race([
indexingDone.then(() => { this.indexingComplete = true; }),
new Promise<void>((resolve) => setTimeout(resolve, STARTUP_TIMEOUT_MS)),
]);
await startupFence;
const transport = new StdioServerTransport();
await this.server.connect(transport);
// Indexing may still be running in background
await indexingDone.then(() => { this.indexingComplete = true; }).catch(() => {});
}
// Add indexing status to list response metadata
handleListResources() {
return {
resources: this.getIndexedResources(),
_meta: this.indexingComplete ? undefined : { indexing: true },
};
}
}**Why**: MCP clients (Claude Desktop) have a 30-second connection timeout. A 10,000-file corpus can take 60+ seconds to index sequentially. Without a startup fence, Claude never connects.
---
Preserve context through async chains.
async function parseDocWithContext(filePath: string): Promise<ParsedDoc> {
try {
const content = await fs.promises.readFile(filePath, 'utf-8');
return parseMarkdownDoc(filePath, content);
} catch (err) {
// Wrap with file context before re-throwing
throw new Error(`Failed to parse ${filePath}: ${(err as Error).message}`, {
cause: err, // Node 16.9+ / ES2022
});
}
}
// Caller can inspect cause:
try {
await parseDocWithContext('/docs/broken.md');
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
// File disappeared between glob and read — normal race condition
}
console.error(err.message); // "Failed to parse /docs/broken.md: ENOENT..."
console.error('Caused by:', (err as Error).cause);
}**Why**: `cause` option (ES2022, Node 16.9+) enables error chains without losing the original. Without it, wrapping with template strings loses the original stack trace.
---
**Detection**:
# Find Promise.all with .map() on potentially large arrays
grep -rn 'Promise\.all.*\.map' --include="*.ts" src/
rg 'Promise\.all\(' --type ts src/ -A2 | grep '\.map'**Signal**:
// Reads ALL files simultaneously — EMFILE on large repos const docs = await Promise.all( allFiles.map((f) => fs.promises.readFile(f, 'utf-8')) );
**Why this matters**: `EMFILE: too many open files` at runtime. Default ulimit is 1024 file descriptors on Linux. A repo with 2000 markdown files triggers this. The error appears non-deterministically depending on OS load, making it hard to reproduce locally.
**Preferred action**:
// Process in batches of 50
for (let i = 0; i < allFiles.length; i += 50) {
await Promise.all(allFiles.slice(i, i + 50).map((f) => this.parseAndCache(f)));
}**Version note**: Node.js 18+ raises default file descriptor limit on Linux but OS limits still apply. Don't rely on this.
---
**Detection**:
grep -rn 'setRequestHandler' --include="*.ts" src/ -A10 | grep -v 'async\|await\|return'
# More specifically: promises not awaited inside handlers
rg 'setRequestHandler.*\{' --type ts src/ -A5 | grep '^\s*[a-zA-Z].*\(\)' | grep -v 'await'**Signal**:
server.setRequestHandler(CallToolRequestSchema, (request) => {
// Not async, not returning promise properly
this.searchIndex(request.params.arguments?.query).then((results) => {
return { content: [{ type: 'text', text: JSON.stringify(results) }] };
});
// Returns undefined! Handler completes before search finishes.
});**Why this matters**: The ha
Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.
Repo: notque/vexjoy-agent
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**:…
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ /…
Universal rules injected by /do at dispatch. Each agent's .md file supplies domain rules.
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix…
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.