Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions app/native-server/src/mcp/mcp-server-stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import * as path from 'path';

let stdioMcpServer: Server | null = null;
let mcpClient: Client | null = null;
let sessionId: string | undefined = undefined;
let isCleaningUp = false;

// Read configuration from stdio-config.json
const loadConfig = () => {
Expand Down Expand Up @@ -65,14 +67,50 @@ export const ensureMcpClient = async () => {
mcpClient = new Client({ name: 'Mcp Chrome Proxy', version: '1.0.0' }, { capabilities: {} });
const transport = new StreamableHTTPClientTransport(new URL(config.url), {});
await mcpClient.connect(transport);
// Save session ID for cleanup on exit
sessionId = transport.sessionId;
return mcpClient;
} catch (error) {
mcpClient?.close();
mcpClient = null;
sessionId = undefined;
console.error('Failed to connect to MCP server:', error);
}
};

// Cleanup function to close session on exit
const cleanup = async () => {
// Prevent concurrent cleanup calls
if (isCleaningUp) return;
isCleaningUp = true;

console.error('[stdio-mcp] Closing session...');
if (sessionId) {
const abortController = new AbortController();
const timeoutId = setTimeout(() => {
abortController.abort();
}, 3000);

try {
const config = loadConfig();
await fetch(config.url, {
method: 'DELETE',
headers: { 'Mcp-Session-Id': sessionId },
signal: abortController.signal,
});
console.error('[stdio-mcp] Session terminated');
} catch (e: any) {
console.error('[stdio-mcp] Failed to terminate session:', e.message);
} finally {
clearTimeout(timeoutId);
}
sessionId = undefined;
}
mcpClient?.close();
stdioMcpServer?.close();
process.exit(0);
};

export const setupTools = (server: Server) => {
// List tools handler
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
Expand Down Expand Up @@ -117,6 +155,26 @@ const handleToolCall = async (name: string, args: any): Promise<CallToolResult>
async function main() {
const transport = new StdioServerTransport();
await getStdioMcpServer().connect(transport);

// Setup stdin handlers to cleanup session on exit
process.stdin.on('end', cleanup);
process.stdin.on('close', cleanup);

// Watchdog for parent PID (backup mechanism)
const parentPid = process.ppid;
const parentCheck = setInterval(() => {
try {
process.kill(parentPid, 0);
} catch (error: any) {
// Only treat ESRCH ("No such process") as a terminated parent.
// Other errors like EPERM mean the process may still exist.
if (error && error.code === 'ESRCH') {
clearInterval(parentCheck);
cleanup();
}
}
}, 10000);
parentCheck.unref();
}

main().catch((error) => {
Expand Down