Anatomi Masalah: MCP Server Hang dan Deployment Stall
Integrasi Model Context Protocol (MCP) pada Claude Code memungkinkan AI mengakses tooling internal via protokol RPC berbasis JSON-RPC melalui stdio atau SSE (Server-Sent Events). Perbedaan mendasar antara CLI wrapper konvensional dan MCP server terletak pada siklus hidup proses (process lifecycle). CLI wrapper dieksekusi sekali dan langsung terminasi (ephemeral), sedangkan MCP server berjalan sebagai long-running daemon.
Ketika plugin MCP mengalami unhandled promise rejection, deadlock koneksi basis data, atau blocking synchronous I/O, proses server tidak mengembalikan respons JSON-RPC dan tetap menahan channel stdio. Kondisi ini menyebabkan pipeline CI/CD atau runtime Claude Code mengalami stall tak terbatas (hanging), menghabiskan runner resources dan memblokir alur rilis.
Langkah 1: Observasi via Structured Logs dan Telemetri Proses
Identifikasi masalah memerlukan inspeksi status proses dan parsing payload log JSON-RPC. Claude Code memetakan server MCP melalui konfigurasi lokal atau project (claude_desktop_config.json atau .claude.json).
Deteksi Deadlock Proses MCP
Jalankan perintah berikut untuk mengidentifikasi proses zombie atau hang yang menahan port atau child process:
# Deteksi proses child MCP yang terikat pada parent Claude Code
ps aux | grep -E "(claude|mcp-server)" | grep -v grep
# Inspeksi status file descriptor dan stdio buffer yang tersumbat
lsof -p <PID_MCP_SERVER> -a -d 0,1,2Parsing Structured Logs MCP
Saat stdout bercampur dengan log non-protokol, framing JSON-RPC rusak. Amati aliran log runtime untuk memvalidasi ketiadaan respons payload:
# Filter log error stderr Claude Code
tail -n 100 -f ~/.claude/logs/mcp*.log | jq -R 'fromjson? | select(.level == "error" or .error != null)'Tanda pasti dari server hang adalah request tools/call tercatat terkirim tanpa adanya pasangan return packet tools/call result hingga timeout tercapai.
Langkah 2: Verifikasi Status Kegagalan Pipeline
Sebelum melakukan intervensi, bedakan antara kegagalan autentikasi upstream, kegagalan transport stdio, dan internal runtime exception. Eksekusi direct healthcheck ping langsung ke interface stdio proses tanpa melewati Claude Code:
# Kirim payload JSON-RPC initialize secara manual untuk menguji responsivitas
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"healthcheck","version":"1.0"}}}' | node ./dist/index.js
# Exit code 1 atau silent hang mengonfirmasi kegagalan runtime MCP serverLangkah 3: Eksekusi Safe Rollback ke CLI Wrapper Statis
Pendekatan mitigasi tercepat (lightest-first) adalah menurunkan dependensi runtime dinamis MCP server kembali ke CLI wrapper statis berbasis command execution. Langkah ini mengeliminasi layer long-running daemon dan memulihkan reliabilitas pipeline.
1. Nonaktifkan Plugin MCP pada Konfigurasi
Edit konfigurasi tooling (misalnya claude.json atau settings tooling runner) untuk mencopot referensi MCP server yang bermasalah:
{
"mcpServers": {
// Hapus atau nonaktifkan server yang bermasalah:
// "internal-tooling": { "command": "node", "args": ["/opt/mcp/index.js"] }
}
}2. Implementasikan Fallback CLI Wrapper Statis
Gantikan pemanggilan via script wrapper deterministik dengan mekanisme timeout native OS:
#!/usr/bin/env bash
# bin/tool-runner-fallback.sh
set -euo pipefail
TIMEOUT_SEC=15
# Eksekusi task statis dengan batasan timeout tegas
if ! timeout "${TIMEOUT_SEC}" npx --yes @internal/legacy-cli "$@"; then
echo "{\"error\": \"Execution timed out or failed with exit code $?\"}" >&2
exit 1
fiCatatan Trade-off: Rollback ke CLI wrapper memutus fitur dynamic capability negotiation dan dynamic schema inference bawaan MCP. AI akan kehilangan kemampuan auto-discovery arguments, tetapi kontinuitas eksekusi pipeline tetap aman dari blocking issue.
Langkah 4: Mitigasi Pasca-Insiden (Postmortem & Hardening)
Untuk mencegah regresi berulang saat MCP server diintegrasikan kembali, terapkan dua kontrol berikut pada layer implementasi server:
1. Defensif Process Timeout Wrapper (Server-Side)
Bungkus execution handlers dengan abort controller dan structured exception boundary:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
const EXECUTION_TIMEOUT_MS = 10000;
async function handleToolCallWithTimeout(request, handler) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), EXECUTION_TIMEOUT_MS);
try {
return await Promise.race([
handler(request, { signal: controller.signal }),
new Promise((_, reject) => {
controller.signal.addEventListener("abort", () =>
reject(new Error(`Tool execution timed out after ${EXECUTION_TIMEOUT_MS}ms`))
);
})
]);
} finally {
clearTimeout(timer);
}
}2. Standardisasi Strict Stdio Isolation
Pastikan kode aplikasi dan dependensi pihak ketiga tidak pernah menulis log diagnostik mentah ke process.stdout. Seluruh log telemetri wajib diarahkan ke process.stderr agar struktur paket JSON-RPC tidak korup:
// Anti-pattern: console.log merusak format payload transport stdio MCP
// console.log("Debug event triggered");
// Best practice: tulis telemetri ke stderr
process.stderr.write(JSON.stringify({ level: "debug", message: "Task processed" }) + "\n");Ringkasan Tindakan Darurat
- Deteksi: Periksa hanging PID dan ketiadaan return packet JSON-RPC pada log.
- Isolasi: Matikan proses hung (
kill -15 <PID>) dan bypass MCP server dari konfigurasi. - Rollback: Alihkan eksekusi ke CLI wrapper statis ber-timeout untuk memulihkan rilis.
- Hardening: Pasang AbortController timeout di dalam server MCP dan pisahkan stdout/stderr secara ketat.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!