Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungWhat is Programming And How i can Enjoy it?(24.09.2026 um 11:54 Uhr)
Sichere ProgrammierungYou Don't Need Adobe Commerce Cloud to Survive Black Friday(24.09.2026 um 11:55 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK cyber capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK Cyber Capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenThe fake worker threat and the rise of human infiltration(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenPolinRider Spreads Through Compromised GitHub Accounts and Packagist(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenWeaselBiscuit Strips BeaverTail and OtterCookie Down to Essentials(24.09.2026 um 11:59 Uhr)
Sichere ProgrammierungWhat is Programming And How i can Enjoy it?(24.09.2026 um 11:54 Uhr)
Sichere ProgrammierungYou Don't Need Adobe Commerce Cloud to Survive Black Friday(24.09.2026 um 11:55 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK cyber capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK Cyber Capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenThe fake worker threat and the rise of human infiltration(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenPolinRider Spreads Through Compromised GitHub Accounts and Packagist(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenWeaselBiscuit Strips BeaverTail and OtterCookie Down to Essentials(24.09.2026 um 11:59 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Fixing Claude Code's SIGINT Problem: How I Built MCP Session Manager

Introduction In my previous article, I implemented a WAL-mode SQLite backend for Memory MCP to solve database locking issues. But that wasn't the end of the story. [MCP Disconnected] memory Connection to MCP server 'memory' was…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Introduction



In my previous article, I implemented a WAL-mode SQLite backend for Memory MCP to solve database locking issues.



But that wasn't the end of the story.




[MCP Disconnected] memory
Connection to MCP server 'memory' was lost






Every time I opened a new Claude Code session, the existing session's MCPs would disconnect. The WAL mode solved database contention, but there was a completely different problem lurking underneath.



Root cause: Claude Code sends SIGINT to existing MCP processes when starting new sessions.



This article explains how I built mcp-session-manager to solve this problem.






The Problem: SIGINT vs Database Lock



Let me clarify the distinction between the two issues:























Problem Cause Solution
database is locked Multiple processes accessing SQLite WAL mode + busy_timeout
MCP Disconnected New session sends SIGINT to existing MCPs This article


Even with WAL mode enabled, if the MCP process itself dies, there's nothing to access the database. I needed to rethink the architecture fundamentally.






The Default Architecture (Problematic)



Here's how Claude Code handles MCPs by default:




Session A (Claude Code Window 1)     Session B (Claude Code Window 2)
| |
v v
[MCP Process A-1] [MCP Process B-1]
[MCP Process A-2] [MCP Process B-2]
[MCP Process A-3] [MCP Process B-3]
| |
+-------- RESOURCE CONFLICT ---------+
|
[SQLite DB File]
[File Watchers]
[In-memory State]






When Session B starts:




  1. Claude Code spawns new MCP processes for Session B


  2. Sends SIGINT to existing MCP processes (for some reason)

  3. Session A's MCPs die

  4. Session A shows "MCP Disconnected" error



You might think "just handle SIGINT with process.on('SIGINT', ...)", but that's not enough. Even if the process survives, resource conflicts (like FileWatcher) remain unsolved.






The Solution: 3-Layer Architecture



The solution is straightforward:



"Each session gets a lightweight proxy; actual processing happens in a shared daemon."




Session A                          Session B
| |
v v
[Proxy A] -------- HTTP -------- [MCP Daemon]
(stdio) shared (HTTP/SSE)
| |
[Claude A] [Claude B]









Design Principles





  1. Singleton Daemons: Each MCP type runs as a single daemon process


  2. Lightweight Proxies: Convert Claude's stdio to HTTP and forward to daemon


  3. SIGINT Immunity: Proxies ignore SIGINT, protecting the shared daemon


  4. Auto-start: Daemons start automatically on first request






Implementation Details






1. SIGINT Handler in Proxy



The most critical part. Set the handler at the very top of the file:




// proxy/index.ts - at the very top
process.on("SIGINT", () => {
// Ignore SIGINT - let the session continue
console.error("[Proxy] Received SIGINT - ignoring for multi-session stability");
});

// Imports come after
import { Command } from "commander";
// ...






Key points:




  • Set before any imports (as early as possible)

  • Log to stderr (stdout is for MCP protocol)

  • Do nothing - just ignore it






2. Transport Support



MCP uses different transport formats. I had to support all of them:
































MCP Port Transport Notes
memory 3100 streamable-http Requires Accept header
code-index 3101 streamable-http SSE response
ast-grep 3102 sse Deprecated format





// proxy/client.ts
export async function sendRequest(
client: DaemonClient,
message: JsonRpcRequest
): Promise<JsonRpcResponse> {
switch (client.transport) {
case "sse":
return sendRequestSSE(client, message);
case "streamable-http":
return sendRequestStreamableHttp(client, message);
case "http":
default:
return sendRequestHttp(client, message);
}
}









3. Streamable-HTTP Transport



Based on MCP 2025-03-26 specification:




async function sendRequestStreamableHttp(
client: DaemonClient,
message: JsonRpcRequest
): Promise<JsonRpcResponse> {
const headers: Record<string, string> = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream" // This is crucial
};

if (client.sessionId) {
headers["Mcp-Session-Id"] = client.sessionId;
}

const response = await fetch(`${client.baseUrl}/mcp`, {
method: "POST",
headers,
body: JSON.stringify(message),
signal: AbortSignal.timeout(60000)
});

// Capture session ID
const sessionId = response.headers.get("Mcp-Session-Id");
if (sessionId) {
client.sessionId = sessionId;
}

const contentType = response.headers.get("Content-Type") || "";

// Handle SSE response
if (contentType.includes("text/event-stream")) {
return await handleSSEResponse(response, message.id);
}

// Handle JSON response
return await response.json() as JsonRpcResponse;
}









4. SSE Transport (Deprecated Format)



The ast-grep-mcp uses FastMCP, which implements the deprecated SSE format:




async function sendRequestSSE(
client: DaemonClient,
message: JsonRpcRequest
): Promise<JsonRpcResponse> {
// Initialize SSE session
if (!client.sseSessionId || !client.sseReader) {
const sessionResult = await initializeSSESession(client);
if (!sessionResult.success) {
return createErrorResponse(message.id, -32603, sessionResult.error);
}
}

// Handle URL format (FastMCP gotcha!)
let messagesUrl: string;
if (client.sseSessionId!.startsWith("?")) {
// Query parameter format: /messages?session_id=...
messagesUrl = `${client.baseUrl}/messages${client.sseSessionId}`;
} else {
// Path format: /messages/<session_id>
messagesUrl = `${client.baseUrl}/messages/${client.sseSessionId}`;
}

// Send request
const response = await fetch(messagesUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message)
});

// Response comes from SSE stream
return await waitForSSEResponse(client, message.id);
}






FastMCP Gotcha:



FastMCP's /sse endpoint returns session ID like this:




event: endpoint
data: /messages/?session_id=abc123






Notice the /messages/?session_id=... format instead of the typical /messages/<session_id>. This cost me 2 hours of debugging.






5. stdin Close Handling



Another gotcha: if the proxy exits immediately when stdin closes, it might interrupt in-flight requests.




let pendingRequests = 0;
let stdinClosed = false;

const checkExit = async () => {
// Only exit when stdin is closed AND all requests are complete
if (stdinClosed && pendingRequests === 0) {
log("All requests completed, cleaning up...");
await closeSession(client);
process.exit(0);
}
};

rl.on("line", async (line) => {
const message = JSON.parse(line) as JsonRpcRequest;

if (message.id !== undefined) {
pendingRequests++;
try {
const response = await sendRequest(client, message);
process.stdout.write(JSON.stringify(response) + "\n");
} finally {
pendingRequests--;
await checkExit();
}
}
});

rl.on("close", async () => {
stdinClosed = true;
await checkExit();
});









6. Auto-start Daemon



The proxy automatically starts the daemon if it's not running:




async function getDaemonInfo(name: string): Promise<{ port: number; transport: TransportType } | null> {
const config = getDaemonConfig(name);
if (!config) return null;

// 1. Ping port to detect existing daemon
const isAliveOnPort = await pingDaemon(config.port, config.transport);
if (isAliveOnPort) {
return { port: config.port, transport: config.transport };
}

// 2. Check lock file
const lockData = readLockFile(name);
if (lockData) {
const isAlive = await pingDaemon(lockData.port, lockData.transport);
if (isAlive) {
return { port: lockData.port, transport: lockData.transport };
}
}

// 3. Try via Manager API
const managerResult = await ensureDaemonViaManager(name);
if (managerResult) return managerResult;

// 4. Fallback: start directly
return startDaemonDirectly(name);
}









7. Per-Project Memory Database



After solving the SIGINT problem, I discovered another issue: memory was being shared across different projects.



Content memorized in Project A was visible in Project B. Not good.






Solution: Propagate Project Path via HTTP Header






// proxy/index.ts - detect and send project path
const projectPath = process.cwd();

const headers: Record<string, string> = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"Mcp-Project-Path": projectPath // Send project path
};









memory-mcp-sqlite Side



Using AsyncLocalStorage to manage per-request context:




import { AsyncLocalStorage } from "node:async_hooks";

interface RequestContext {
projectPath?: string;
}

const asyncLocalStorage = new AsyncLocalStorage<RequestContext>();

// Set context in request handler
app.use((req, res, next) => {
const projectPath = req.headers["mcp-project-path"] as string | undefined;
asyncLocalStorage.run({ projectPath }, () => next());
});

// Get DB path from context
function getDbPath(): string {
const context = asyncLocalStorage.getStore();
if (context?.projectPath) {
const projectDbPath = path.join(context.projectPath, ".claude", "memory.db");
if (canWriteTo(projectDbPath)) {
return projectDbPath;
}
}
return path.join(os.homedir(), ".claude", "memory.db");
}









Store Caching



Efficiently manage DB connections for multiple projects:




const storeCache = new Map<string, KnowledgeGraphStore>();

function getStore(dbPath: string): KnowledgeGraphStore {
if (!storeCache.has(dbPath)) {
storeCache.set(dbPath, new KnowledgeGraphStore(dbPath));
}
return storeCache.get(dbPath)!;
}









DB Path Priority




















Condition DB Path
Project path exists & writable <project>/.claude/memory.db
Otherwise ~/.claude/memory.db


Benefits:




  • Memory doesn't mix between projects

  • Backward compatible with existing global DB

  • Users automatically get project isolation






Usage






Install






npm install -g mcp-session-manager









Generate Config






mcp-manager generate-config






This creates ~/.claude/mcp.json:




{
"mcpServers": {
"memory": {
"command": "node",
"args": ["/path/to/mcp-session-manager/dist/proxy/index.js", "--target", "memory"]
},
"code-index": {
"command": "node",
"args": ["/path/to/mcp-session-manager/dist/proxy/index.js", "--target", "code-index"]
},
"ast-grep": {
"command": "node",
"args": ["/path/to/mcp-session-manager/dist/proxy/index.js", "--target", "ast-grep"]
}
}
}









Restart Claude Code



Restart to apply the new configuration.






Verify



Open multiple Claude Code sessions - they should all work simultaneously without disconnections.






Troubleshooting






Check daemon status






curl http://localhost:3199/status









View daemon logs







type %USERPROFILE%\.mcp-session-manager\memory.log

# macOS/Linux
cat ~/.mcp-session-manager/memory.log









Remove stale lock files






# Windows
del %USERPROFILE%\.mcp-session-manager\*.lock

# macOS/Linux
rm ~/.mcp-session-manager/*.lock









Summary



I built mcp-session-manager to solve Claude Code's SIGINT problem.



Key points:




  • Proxy layer that ignores SIGINT

  • Singleton daemon shared by all sessions

  • Multiple transport support (HTTP, Streamable-HTTP, SSE)

  • Auto-start with lock file exclusion


  • Per-project memory DB: AsyncLocalStorage for per-request DB switching



Combined with the previous WAL mode implementation, Claude Code's multi-session and multi-project operation is now fully stable.






Bonus: Auto-start Daemons on Terminal Launch (Windows)



Manually starting daemons every time is tedious. I added an auto-start script to my PowerShell profile.






Add to PowerShell Profile



Add to $PROFILE (usually ~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1):




function Start-McpDaemonsIfNeeded {
$mcpDir = "C:\path\to\mcp-session-manager"
$lockFile = "$env:TEMP\mcp-daemons-starting.lock"
$lockTimeout = 120 # seconds

# Check if ports are already listening
try {
$port3101 = Get-NetTCPConnection -LocalPort 3101 -State Listen -ErrorAction SilentlyContinue
$port3102 = Get-NetTCPConnection -LocalPort 3102 -State Listen -ErrorAction SilentlyContinue
} catch {}

# If both ports are listening, daemons are running
if ($port3101 -and $port3102) {
Write-Host "[MCP] Daemons already running" -ForegroundColor Green
return
}

# Lock file prevents duplicate startup
if (Test-Path $lockFile) {
$elapsed = (Get-Date) - (Get-Item $lockFile).LastWriteTime
if ($elapsed.TotalSeconds -lt $lockTimeout) {
Write-Host "[MCP] Daemons starting..." -ForegroundColor Yellow
return
}
Remove-Item $lockFile -Force -ErrorAction SilentlyContinue
}

# Create lock file and start in new terminal
New-Item -ItemType File -Path $lockFile -Force | Out-Null
Start-Process pwsh -ArgumentList "-NoExit", "-Command", "cd '$mcpDir'; .\start-daemons.ps1"
}

Start-McpDaemonsIfNeeded









Key Points





  1. Port check: Skip if daemons are already running


  2. Lock file: Prevents duplicate startup when opening multiple terminals simultaneously


  3. Timeout: 120-second lock expiration for crash recovery


  4. Separate terminal: Start-Process pwsh opens daemons in a new window



Now daemons start automatically when you open a terminal.






Resources








Read on Other Platforms








I hope this helps anyone struggling with multi-session Claude Code operation. Issues and PRs welcome!






About the Author



Daichi Kudo





  • Cognisant LLC CEO - Building the future where humans and AI create together


  • M16 LLC CTO - AI, Creative, and Engineering

CTI Threat Relationship Graph7 Knoten / 6 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
SOC Incident Playbook: Remote Code Execution (RCE) Defense
title: Detect Exploitation - Fixing Claude Code's SIGINT Problem: How I Built MCP Session Manager
id: 3a5654ae-8296-43a7-ba05-3b345a0f0da4
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
  - attack.t1059
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "Fixing Claude Code\'s SIGINT Pr" ascii wide
    condition:
        any of them
}
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Fixing Claude Code&#039;s SIGINT Problem: How.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Fixing Claude Code's SIGINT Problem: How I Built MCP Session Manager

Thematisch verwandte Begriffe: Fixing, Claude, Codes, SIGINT · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-96891 | A vulnerability was identified in D-Link DIR-825 3.00b32. Affected is th…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick