feat: v1.3 advanced features + WebSocket transport + batch command#12
feat: v1.3 advanced features + WebSocket transport + batch command#12
Conversation
…ay resilience, output formats, skill auto-regen Phase 15 - Schema Intelligence (ADV-01, ADV-02): - Schema caching with 24hr TTL at ~/.cache/mcp2cli/schemas/ - SHA-256 drift detection with per-tool change alerts - Cache clear/status commands, --fresh bypass flag Phase 16 - Access Control & Discovery (ADV-05, INFRA-03): - allowTools/blockTools glob patterns in services.json - Cross-service tool search via `mcp2cli grep` - TOOL_BLOCKED errors for policy-restricted tools Phase 17 - Gateway Resilience (INFRA-01, INFRA-02): - HTTP gateway with stdio fallback config - Circuit breaker (5 failures, 60s cooldown, disk-persisted) Phase 18 - Output Formats (ADV-03, ADV-04): - --format table/yaml/csv/ndjson alongside default json - Errors always JSON regardless of format flag Phase 19 - Skill Auto-Regeneration (ADV-06): - Drift-triggered skill file regeneration - Manual section preservation via MANUAL:START/END markers - --diff preview flag, access control integration Security hardening from agent swarm review: - Path traversal protection on cache/circuit-breaker file paths - Input validation on schema command service/tool names - ReDoS mitigation in glob patterns (collapse wildcards, length guard, cache) - Circuit breaker state enum validation - YAML key escaping for injection prevention - Exhaustiveness guard on format switch 678 tests, 0 failures, 0 TypeScript errors across 79 source files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion Examples: - examples/services-basic.json -- single service config - examples/services-multi.json -- multi-service with access control - examples/services-http-fallback.json -- HTTP gateway with stdio fallback - examples/skills/qmd/ -- real generated skill for QMD (6 tools, 247 tokens) - examples/skills/n8n/ -- real generated skill for n8n (20 tools, 467 tokens) - examples/skills/vaultwarden-secrets/ -- real generated skill (11 tools, 328 tokens) Test isolation: - Fix 4 daemon test files leaking cache artifacts into ~/.cache/mcp2cli/ - All tests now use isolated MCP2CLI_CACHE_DIR via mkdtemp README: - Add description fields to all example config blocks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WebSocket transport: - New "websocket" backend type in services.json config - Uses SDK's built-in WebSocketClientTransport (persistent connection) - Fallback to stdio with circuit breaker (same as HTTP) - Wired into daemon pool, schema, service-help, tool-call, generate-skills Batch command: - `mcp2cli batch` reads NDJSON tool calls from stdin - Executes sequentially by default, --parallel for concurrent execution - Outputs NDJSON results with service.tool correlation - Individual errors don't abort the batch 703 tests, 0 failures, 0 TypeScript errors across 81 source files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…solution failure The barrel re-export from connection/index.ts caused a cascading module evaluation failure in CI when the SDK's WebSocket module couldn't load. Direct imports from websocket-transport.ts avoid this -- callers only load the module when a websocket backend is actually configured. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…kflow The claude-code-action expands 'sonnet' to 'claude-sonnet-4-6' internally, which isn't a registered alias in our LiteLLM proxy. Using 'sonnet4.6' and 'opus4.6' directly matches the LiteLLM model registry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude re-review |
|
Claude finished @rodaddy's task —— View job PR ReviewTasks
SummaryOverall this is a well-structured PR with good security awareness (path traversal guards, atomic writes, ReDoS mitigation). Below are findings ranging from bugs to hardening opportunities. Bugs / Logic Errors1. Path traversal guard is insufficient — The current check only blocks if (service.includes('/') || service.includes('\\') || service.includes('..')) {A service name like if (!/^[a-zA-Z0-9_\-]+$/.test(service)) {
throw new Error(`Invalid service name: "${service}"`);
}2. Uncleaned timer in WebSocket transport —
function rejectAfter(ms: number, url: string): Promise<never> {
let timer: ReturnType<typeof setTimeout>;
return Object.assign(
new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(...), ms);
}),
{ cancel: () => clearTimeout(timer) }
);
}Or wrap the entire race in a helper that clears the timeout on resolution. 3. Health-check timer also leaks — The 4.
const MAX_BATCH_BYTES = 10 * 1024 * 1024;
let totalBytes = 0;
for await (const chunk of process.stdin) {
totalBytes += chunk.length;
if (totalBytes > MAX_BATCH_BYTES) throw new Error("Batch input too large");
chunks.push(chunk as Buffer);
}5. Patterns come from config, so this is low risk in practice, but if patterns change at runtime the cache grows without bound. Consider a small LRU cap or clear on config reload. 6.
Security Issues7. Service name used in log output without sanitization
8.
9. YAML key escaping covers values but not all key edge cases
Resource Leaks10. WebSocket connection leaked when pool rejects at capacity — When 11. Drift hook writes cache even when drift check fails mid-way In Error Handling12.
13. This is by design ("fire-and-forget"), but the catch only logs a warning. If 14. The TypeScript Strictness15.
16.
17. if (typeof data[0] !== "object" || data[0] === null) {
return formatPrimitiveCsv(data);
}
return formatArrayCsv(data as Record<string, unknown>[]);Only the first element is checked; the rest of the array could contain mixed types (e.g. Minor Observations
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Claude finished @rodaddy's task —— View job PR ReviewTasks
SummaryThis is a well-structured PR with clear architecture and good security awareness throughout. The code is readable and consistently organized. Findings below range from confirmed bugs to hardening opportunities. Bugs / Logic Errors1. Path traversal guard uses blocklist instead of allowlist — The current check blocks specific characters but a name like if (!/^[a-zA-Z0-9_\-]+$/.test(service)) {
throw new Error(`Invalid service name: "${service}"`);
}2. When async function withTimeout<T>(promise: Promise<T>, ms: number, url: string): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new ConnectionError(`timed out after ${ms}ms`, `url: ${url}`)), ms);
});
try {
return await Promise.race([promise, timeout]);
} finally {
clearTimeout(timer);
}
}3. The 4.
const MAX_BATCH_BYTES = 10 * 1024 * 1024; // 10MB
let totalBytes = 0;
for await (const chunk of process.stdin) {
totalBytes += (chunk as Buffer).length;
if (totalBytes > MAX_BATCH_BYTES) {
throw new Error("Batch input exceeds 10MB limit");
}
chunks.push(chunk as Buffer);
}5.
6.
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
results.push({ error: `Expected a JSON object on line ${i + 1}`, line: i + 1 });
continue;
}Security Issues7. The } catch (err) {
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
return 0;
}8. The regex if (value.startsWith("-") || value.startsWith(".")) return true;9. In a long-running process where configs are dynamically reloaded, this cache grows without bound. Since service configs are trusted this is low severity, but worth noting if the daemon is ever embedded in a server process. 10. The comment says "go up one level for sibling," but Resource / Connection Leaks11. The rejection handler for 12.
TypeScript / Type Safety13. Node's async iteration over 14. Unsafe first-element check for array type discrimination — if (typeof data[0] !== "object" || data[0] === null) {
return formatPrimitiveCsv(data);
}
return formatArrayCsv(data as Record<string, unknown>[]);Only 15.
Minor Observations
|

Summary
WebSocketClientTransportwith stdio fallbackmcp2cli batchreads NDJSON from stdin for multi-call execution (sequential or--parallel)Stats
Test plan
🤖 Generated with Claude Code