Skip to main content
Glama
ClaudeCodeAdapter.ts5.51 kB
/** * ClaudeCodeAdapter - Unified adapter for Claude Code hook format (PreToolUse & PostToolUse) * * DESIGN PATTERNS: * - Adapter pattern: Converts Claude Code format to normalized format * - Parser pattern: Extracts file paths and operations from tool inputs * - State pattern: Stores hook event type to morph behavior between PreToolUse and PostToolUse * * CODING STANDARDS: * - Parse Claude Code JSON stdin format exactly as specified * - Format output to match Claude Code hook response schema * - Handle missing/optional fields gracefully * - Support both PreToolUse and PostToolUse events in one adapter * * AVOID: * - Assuming all fields are present * - Hardcoding tool names (use constants if needed) * - Mutating input objects */ import { BaseAdapter } from './BaseAdapter'; import type { HookResponse } from '../types'; import { log } from '@agiflowai/aicode-utils'; /** * Claude Code hook input format (PreToolUse) */ export interface ClaudeCodePreToolUseInput { tool_name: string; tool_input: Record<string, any>; cwd: string; session_id: string; hook_event_name: 'PreToolUse'; tool_use_id: string; transcript_path: string; permission_mode: string; llm_tool?: string; } /** * Claude Code hook input format (PostToolUse) */ export interface ClaudeCodePostToolUseInput { tool_name: string; tool_input: Record<string, any>; tool_response: Record<string, any>; cwd: string; session_id: string; hook_event_name: 'PostToolUse'; tool_use_id: string; transcript_path: string; permission_mode: string; llm_tool?: string; } /** * Union type for both hook input formats */ export type ClaudeCodeHookInput = ClaudeCodePreToolUseInput | ClaudeCodePostToolUseInput; /** * Claude Code PreToolUse hook output format */ interface ClaudeCodePreToolUseOutput { hookSpecificOutput: { hookEventName: 'PreToolUse'; permissionDecision: 'allow' | 'deny' | 'ask'; permissionDecisionReason: string; updatedInput?: Record<string, any>; }; } /** * Claude Code PostToolUse hook output format */ interface ClaudeCodePostToolUseOutput { decision?: 'block'; reason?: string; hookSpecificOutput: { hookEventName: 'PostToolUse'; additionalContext?: string; }; } /** * Unified adapter for Claude Code hook format (PreToolUse & PostToolUse) */ export class ClaudeCodeAdapter extends BaseAdapter<ClaudeCodeHookInput> { private hookEventName: 'PreToolUse' | 'PostToolUse' = 'PreToolUse'; /** * Parse Claude Code stdin into ClaudeCodeHookInput * * @param stdin - Raw JSON string from Claude Code * @returns ClaudeCodeHookInput */ parseInput(stdin: string): ClaudeCodeHookInput { log.debug('ClaudeCodeAdapter: Parsing input', { stdin }); const input = JSON.parse(stdin) as ClaudeCodeHookInput; // Store hook event type for use in formatOutput this.hookEventName = input.hook_event_name; log.debug('ClaudeCodeAdapter: Parsed input', { hookEventName: this.hookEventName, input, }); return input; } /** * Format normalized HookResponse into Claude Code output * Morphs output based on hook event type (PreToolUse vs PostToolUse) * * @param response - Normalized hook response * @returns JSON string for Claude Code */ formatOutput(response: HookResponse): string { log.debug('ClaudeCodeAdapter: Formatting output', { hookEventName: this.hookEventName, response, }); // If decision is 'skip', return empty object (hook has nothing to say) if (response.decision === 'skip') { const emptyOutput = JSON.stringify({}, null, 2); log.debug('ClaudeCodeAdapter: Skip decision, returning empty output'); return emptyOutput; } // Format based on hook event type if (this.hookEventName === 'PostToolUse') { return this.formatPostToolUseOutput(response); } return this.formatPreToolUseOutput(response); } /** * Format PreToolUse output */ private formatPreToolUseOutput(response: HookResponse): string { const output: ClaudeCodePreToolUseOutput = { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: response.decision as 'allow' | 'deny' | 'ask', permissionDecisionReason: response.message, }, }; // Add updated input if provided if (response.updatedInput) { output.hookSpecificOutput.updatedInput = response.updatedInput; } const formattedOutput = JSON.stringify(output, null, 2); log.debug('ClaudeCodeAdapter: Formatted PreToolUse output', { output: formattedOutput }); return formattedOutput; } /** * Format PostToolUse output */ private formatPostToolUseOutput(response: HookResponse): string { const output: ClaudeCodePostToolUseOutput = { hookSpecificOutput: { hookEventName: 'PostToolUse', }, }; // Only include decision and reason if decision is 'deny' (maps to 'block' for PostToolUse) if (response.decision === 'deny') { output.decision = 'block'; output.reason = response.message; } // Include additional context if we want to provide feedback without blocking if (response.decision === 'allow' && response.message) { output.hookSpecificOutput.additionalContext = response.message; } const formattedOutput = JSON.stringify(output, null, 2); log.debug('ClaudeCodeAdapter: Formatted PostToolUse output', { output: formattedOutput }); return formattedOutput; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AgiFlow/aicode-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server