ansible-automation-eng…
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Core MCP SDK patterns for TypeScript/Node.js server implementation, tool registration, and resource handling
$ 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.
Core MCP SDK patterns for TypeScript/Node.js server implementation, tool registration, and resource handling
description: Core MCP SDK patterns for TypeScript/Node.js server implementation, tool registration, and resource handling
> **Scope**: TypeScript/Node.js MCP via `@modelcontextprotocol/sdk` 0.5.0+.
| Pattern | Version | Use When | Avoid When | |---------|---------|----------|------------| | `StdioServerTransport` | 0.5.0+ | Claude Desktop integration | HTTP API needed | | `SSEServerTransport` | 0.5.0+ | Web-based clients, multiple connections | Single-process use | | `server.setRequestHandler()` | 0.5.0+ | All request handling | Direct protocol manipulation | | `ListResourcesRequestSchema` | 0.5.0+ | Exposing documents/files | Structured data with tools | | `CallToolRequestSchema` | 0.5.0+ | Search, filtering, actions | Read-only resource access |
---
Explicit JSON Schema for client-side validation and LLM parameter guidance.
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const SearchSchema = z.object({
query: z.string().min(1).describe('Search terms'),
limit: z.number().int().min(1).max(100).default(10).describe('Max results'),
tags: z.array(z.string()).optional().describe('Filter by tags'),
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'search_docs') {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
// Validate with Zod before processing
const args = SearchSchema.parse(request.params.arguments ?? {});
const results = await searchIndex(args.query, args.limit, args.tags);
return {
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
};
});**Why**: Without schema, malformed arguments cause cryptic errors instead of `InvalidParams`.
---
`docs://` scheme — never expose filesystem paths.
function pathToUri(docsRoot: string, filePath: string): string {
const relative = path.relative(docsRoot, filePath);
// docs://guides/api-reference.md — portable, no host path leakage
return `docs://${relative.replace(path.sep, '/')}`;
}
function uriToPath(docsRoot: string, uri: string): string {
if (!uri.startsWith('docs://')) {
throw new McpError(ErrorCode.InvalidParams, `Invalid URI scheme: ${uri}`);
}
const relative = uri.slice('docs://'.length);
// Prevent path traversal
const resolved = path.resolve(docsRoot, relative);
if (!resolved.startsWith(docsRoot)) {
throw new McpError(ErrorCode.InvalidParams, 'Path traversal not allowed');
}
return resolved;
}**Why**: `file:///` exposes filesystem layout. `docs://` is portable.
---
Serve partial results for large corpora while indexing continues.
class DocsServer {
private indexReady = false;
private indexingPromise: Promise<void> | null = null;
async start(): Promise<void> {
// Begin indexing, but don't await completion — serve partial results
this.indexingPromise = this.indexDocs();
// Wait up to 10s for initial batch, then proceed
await Promise.race([
this.indexingPromise,
new Promise<void>((resolve) => setTimeout(resolve, 10_000)),
]);
this.indexReady = true;
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
private async indexDocs(): Promise<void> {
const files = await glob('**/*.md', { cwd: this.docsPath });
// Process in batches of 50 to avoid memory spikes
for (let i = 0; i < files.length; i += 50) {
const batch = files.slice(i, i + 50);
await Promise.all(batch.map((f) => this.parseAndCache(f)));
}
}
}**Why**: Full indexing before connect causes client timeouts. Batch processing prevents heap exhaustion.
---
**Detection**:
grep -rn 'readFileSync\|existsSync\|readdirSync' --include="*.ts" src/
rg 'Sync\(' --type ts src/**Signal**:
server.setRequestHandler(ReadResourceRequestSchema, (request) => {
// NOT async — blocks the entire Node.js event loop
const content = fs.readFileSync(uriToPath(request.params.uri), 'utf-8');
return { contents: [{ uri: request.params.uri, text: content }] };
});**Why this matters**: `readFileSync` blocks the event loop. While one client is reading a large file, all other clients are frozen. Under concurrent load this causes cascading timeouts — the MCP client kills the connection thinking the server hung.
**Preferred action**:
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const filePath = uriToPath(this.docsRoot, request.params.uri);
const content = await fs.promises.readFile(filePath, 'utf-8');
return { contents: [{ uri: request.params.uri, text: content }] };
});---
**Detection**:
grep -rn 'indexDocs\|parseDoc\|WalkDir' --include="*.ts" src/ # Flag if found inside a setRequestHandler callback
**Signal**:
server.setRequestHandler(ListResourcesRequestSchema, async () => {
// Re-parses all markdown files on every list call — seconds of delay
const docs = await this.indexDocs();
return { resources: docs.map((d) => ({ uri: d.uri, name: d.metadata.title })) };
});**Why this matters**: Claude calls `resources/list` repeatedly during a session. Re-parsing 1000 files takes 3-10 seconds each time. The LLM context window fills with timeout errors before any content arrives.
**Preferred action**: Index once at startup, serve from in-memory Map, use mtime-based cache invalidation:
private docsIndex = new Map<string, ParsedDoc>();
// Called once at startup
async indexDocs(): Promise<void> {
const files = await glob('**/*.md', { cwd: this.docsPath });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.