import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
/**
* Generate signal trace instrumentation code for Gerbil.
*/
function generateSignalTraceCode(signals: string[]): string {
const timestamp = `(time->seconds (current-time))`;
const code = `;;; Signal Trace Instrumentation
;;; Auto-generated by gerbil_signal_trace
;;;
;;; Import this module and call (enable-signal-tracing!) before your main code.
;;; Call (disable-signal-tracing!) to stop tracing.
;;; Call (dump-signal-trace) to print the trace log.
(import :std/sugar
:std/misc/process
(only-in :std/srfi/19 current-time time->seconds))
(export enable-signal-tracing!
disable-signal-tracing!
dump-signal-trace)
;; Global trace log
(def *signal-trace* (box []))
(def *tracing-enabled* (box #f))
(def (log-trace! event)
(when (unbox *tracing-enabled*)
(let ((timestamp ${timestamp})
(entry (hash 'time timestamp 'event event)))
(set-box! *signal-trace* (cons entry (unbox *signal-trace*))))))
;; Instrumented signal handlers
${signals
.map(
(sig) => `
(def *original-${sig.toLowerCase()}-handler* #f)
(def (traced-${sig.toLowerCase()}-handler)
(log-trace! (hash 'type 'signal-received 'signal '${sig}))
(when *original-${sig.toLowerCase()}-handler*
(with-catch
(lambda (e)
(log-trace! (hash 'type 'signal-handler-exception 'signal '${sig} 'error (error-message e)))
(raise e))
(lambda ()
(*original-${sig.toLowerCase()}-handler*)
(log-trace! (hash 'type 'signal-handled 'signal '${sig}))))))
`,
)
.join('')}
(def (enable-signal-tracing!)
(set-box! *tracing-enabled* #t)
(set-box! *signal-trace* [])
(log-trace! (hash 'type 'tracing-started))
${signals
.map(
(sig) =>
` (set! *original-${sig.toLowerCase()}-handler* (signal-handler ${sig}))
(signal-handler ${sig} traced-${sig.toLowerCase()}-handler)`,
)
.join('\n')}
(log-trace! (hash 'type 'handlers-installed 'signals '${signals.join(' ')}))
(displayln "[Signal Trace] Tracing enabled for: ${signals.join(' ')}"))
(def (disable-signal-tracing!)
(log-trace! (hash 'type 'tracing-stopped))
(set-box! *tracing-enabled* #f)
${signals
.map(
(sig) =>
` (when *original-${sig.toLowerCase()}-handler*
(signal-handler ${sig} *original-${sig.toLowerCase()}-handler*))`,
)
.join('\n')}
(displayln "[Signal Trace] Tracing disabled"))
(def (dump-signal-trace)
(displayln "\\n=== Signal Trace Log ===")
(let ((trace (reverse (unbox *signal-trace*))))
(if (null? trace)
(displayln " (no events recorded)")
(for-each
(lambda (entry)
(let ((time (hash-ref entry 'time))
(event (hash-ref entry 'event)))
(displayln " [" (number->string time 10) "] " (call-with-output-string (lambda (p) (write event p))))))
trace)))
(displayln "=== End Trace ===\\n"))
`;
return code;
}
export function registerSignalTraceTool(server: McpServer): void {
server.registerTool(
'gerbil_signal_trace',
{
title: 'Signal Trace Instrumentation Generator',
description:
'Generate Gerbil code for tracing signal delivery and trap execution. ' +
'Creates an instrumentation module that logs when signals are received, ' +
'when handlers execute, and whether exceptions occur. Useful for debugging ' +
'signal handling issues like unresponsive CTRL-C, trap execution failures, ' +
'or process hangs. Outputs a complete Gerbil module that can be imported ' +
'into your code for runtime signal tracing.',
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
inputSchema: {
signals: z
.array(z.string())
.min(1)
.describe(
'List of signals to trace (e.g., ["SIGTERM", "SIGINT", "SIGCHLD"])',
),
},
},
async ({ signals }) => {
if (signals.length === 0) {
return {
content: [
{
type: 'text' as const,
text: 'Error: Must specify at least one signal to trace.',
},
],
isError: true,
};
}
const code = generateSignalTraceCode(signals);
const sections: string[] = [
`Signal Trace Instrumentation Code`,
``,
`Tracing signals: ${signals.join(', ')}`,
``,
`Usage:`,
` 1. Save this code to a file (e.g., signal-trace.ss)`,
` 2. Import it in your code: (import :yourpkg/signal-trace)`,
` 3. Call (enable-signal-tracing!) before your main logic`,
` 4. Trigger signals (e.g., CTRL-C, kill commands)`,
` 5. Call (dump-signal-trace) to see the event log`,
` 6. Call (disable-signal-tracing!) when done`,
``,
`Generated Code:`,
``,
code,
];
return {
content: [{ type: 'text' as const, text: sections.join('\n') }],
};
},
);
}