Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@civic/hook-common

Package Overview
Dependencies
Maintainers
13
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@civic/hook-common

Common utilities and types for implementing MCP server hooks

latest
Source
npmnpm
Version
0.9.0
Version published
Weekly downloads
230
-4.96%
Maintainers
13
Weekly downloads
 
Created
Source

@civic/hook-common

Common utilities and types for implementing MCP (Model Context Protocol) server hooks.

This package provides the core functionality for creating hooks that can intercept and modify tool calls in MCP servers.

Think of it as a middleware layer that allows you to analyze, modify, or validate tool calls before they are executed, and to process the responses from those tool calls after execution.

It is designed to be used in combination with the Passthrough Proxy MCP server.

Installation

pnpm add @civic/hook-common

Overview

The hook-common package provides:

  • Type definitions for tool calls, hook responses, and metadata
  • Base client class for implementing hooks
  • Utilities for processing and validating hook interactions

Core Concepts

Tool Calls

Tool calls use the MCP SDK types directly:

import type { CallToolRequest, CallToolResult } from "@modelcontextprotocol/sdk/types.js";

// Tool call request structure
interface CallToolRequest {
  method: "tools/call";
  params: {
    name: string;
    arguments?: unknown;
    _meta?: {
      sessionId?: string;
      // other metadata
    };
  };
}

Hook Results

Hooks return discriminated unions based on the result type:

// For request processing
type ToolCallRequestHookResult =
  | { resultType: "continue"; request: CallToolRequest }
  | { resultType: "abort"; reason: string; body?: unknown }
  | { resultType: "respond"; response: CallToolResult }
  | {
      resultType: "continueAsync";
      request: CallToolRequest;
      response: CallToolResult;
      callback: (response: CallToolResult | null, error: HookChainError | null) => void | Promise<void>;
    };

// For response processing
type ToolCallResponseHookResult =
  | { resultType: "continue"; response: CallToolResult }
  | { resultType: "abort"; reason: string; body?: unknown };

continueAsync Result Type (v0.7.0+)

The continueAsync result type allows a hook to return an immediate response to the client while continuing async processing through the remaining hooks:

async processCallToolRequest(
  request: CallToolRequest,
  requestExtra: RequestExtra
): Promise<CallToolRequestHookResult> {
  return {
    resultType: "continueAsync",
    request,
    response: {
      content: [{ type: "text", text: "Processing started..." }]
    },
    callback: async (finalResponse, error) => {
      if (error) {
        console.error("Processing failed:", error);
        // Handle error (e.g., send notification, log to external service)
      } else {
        console.log("Processing completed:", finalResponse);
        // Handle success (e.g., update database, send notification)
      }
    }
  };
}

Important Limitation: The continueAsync result type is NOT supported over tRPC (RemoteHookClient). Callbacks cannot be serialized and sent over the network. Only use continueAsync with:

  • Local hooks (LocalHookClient)
  • Direct Hook instances

For remote hooks, use respond, continue, or abort result types instead.

Hook Interface

The interface for implementing hooks (v0.4.1+):

interface RequestExtra {
  requestId: string | number;
  sessionId?: string;
}

interface Hook {
  get name(): string;
  
  // Request processing methods - receive RequestExtra as second parameter
  processCallToolRequest?(
    request: CallToolRequest, 
    requestExtra: RequestExtra
  ): Promise<CallToolRequestHookResult>;
  
  processListToolsRequest?(
    request: ListToolsRequest,
    requestExtra: RequestExtra
  ): Promise<ListToolsRequestHookResult>;
  
  processInitializeRequest?(
    request: InitializeRequest,
    requestExtra: RequestExtra
  ): Promise<InitializeRequestHookResult>;
  
  processListResourcesRequest?(
    request: ListResourcesRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ListResourcesRequestHookResult>;
  
  processListResourceTemplatesRequest?(
    request: ListResourceTemplatesRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ListResourceTemplatesRequestHookResult>;
  
  processReadResourceRequest?(
    request: ReadResourceRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ReadResourceRequestHookResult>;
  
  // Response processing methods - receive RequestExtra as third parameter
  processCallToolResult?(
    response: CallToolResult,
    originalCallToolRequest: CallToolRequest,
    requestExtra: RequestExtra
  ): Promise<CallToolResponseHookResult>;
  
  processListToolsResult?(
    response: ListToolsResult,
    originalRequest: ListToolsRequest,
    requestExtra: RequestExtra
  ): Promise<ListToolsResponseHookResult>;
  
  processInitializeResult?(
    response: InitializeResult,
    originalRequest: InitializeRequest,
    requestExtra: RequestExtra
  ): Promise<InitializeResponseHookResult>;
  
  processListResourcesResult?(
    response: ListResourcesResult,
    originalRequest: ListResourcesRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ListResourcesResponseHookResult>;
  
  processListResourceTemplatesResult?(
    response: ListResourceTemplatesResult,
    originalRequest: ListResourceTemplatesRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ListResourceTemplatesResponseHookResult>;
  
  processReadResourceResult?(
    response: ReadResourceResult,
    originalRequest: ReadResourceRequestWithContext,
    requestExtra: RequestExtra
  ): Promise<ReadResourceResponseHookResult>;
  
  // ... other methods follow the same pattern
}

Creating a Hook

To create a custom hook, extend the AbstractHook class:

import { AbstractHook } from '@civic/hook-common';
import type { 
  CallToolRequest, 
  CallToolResult,
  RequestExtra,
  CallToolRequestHookResult,
  CallToolResponseHookResult 
} from '@civic/hook-common';

export class MyCustomHook extends AbstractHook {
  get name(): string {
    return 'my-custom-hook';
  }

  async processCallToolRequest(
    request: CallToolRequest,
    requestExtra: RequestExtra
  ): Promise<CallToolRequestHookResult> {
    // Use requestId for tracking
    console.log(`[${requestExtra.requestId}] Processing request for tool: ${request.params.name}`);
    console.log(`Session ID: ${requestExtra.sessionId}`);
    
    // Optionally modify the tool call
    const modifiedRequest = {
      ...request,
      params: {
        ...request.params,
        arguments: {
          ...request.params.arguments,
          injected: 'value'
        }
      }
    };

    // Return response
    return {
      resultType: 'continue',
      request: modifiedRequest
    };
  }

  async processCallToolResult(
    response: CallToolResult,
    originalCallToolRequest: CallToolRequest,
    requestExtra: RequestExtra
  ): Promise<CallToolResponseHookResult> {
    // Use the same requestId to correlate with the request
    console.log(`[${requestExtra.requestId}] Processing response for tool: ${originalCallToolRequest.params.name}`);
    
    // Optionally modify the response
    return {
      resultType: 'continue',
      response: response
    };
  }
}

Request Tracking with RequestExtra (v0.4.1+)

The RequestExtra parameter enables powerful request tracking capabilities:

  • Request ID: Unique identifier for each request, enabling correlation between request and response processing
  • Session ID: Consistent identifier throughout a client session, useful for session-based analytics

Use Cases

  • Request/Response Correlation: Match responses to their originating requests without maintaining state
  • Distributed Tracing: Track requests across multiple hooks and services
  • Rate Limiting: Implement per-session or per-request rate limiting
  • Audit Logging: Create comprehensive audit trails with request/session context
  • Performance Monitoring: Measure processing time for each request

Hook Execution Flow

  • Request Processing: When a tool is called, hooks process the request in order

  • Tool Execution: If all hooks return "continue", the tool executes

  • Response Processing: Hooks process the tool's response in reverse order

  • Error Processing (v0.4.2+): Hooks can intercept and handle errors through dedicated error callbacks

graph LR
    A[Tool Call] --> B[Hook 1 Request]
    B --> C[Hook 2 Request]
    C --> D[Tool Execution]
    D --> E[Hook 2 Response/Error]
    E --> F[Hook 1 Response/Error]
    F --> G[Final Response]

Error Handling (v0.4.2+)

Hooks can now process errors that occur during request processing:

interface Hook {
  // Error processing methods
  processCallToolError?(
    error: HookChainError,
    originalRequest: CallToolRequest,
    requestExtra: RequestExtra
  ): Promise<CallToolErrorHookResult>;
  
  processListToolsError?(
    error: HookChainError,
    originalRequest: ListToolsRequest,
    requestExtra: RequestExtra
  ): Promise<ListToolsErrorHookResult>;
  
  // ... other error methods
}

Error processing allows hooks to:

  • Transform errors: Modify error messages or codes
  • Recover from errors: Convert an error into a successful response
  • Pass through errors: Let the error continue unchanged
export class ErrorHandlingHook extends AbstractHook {
  async processCallToolError(
    error: HookChainError,
    originalRequest: CallToolRequest,
    requestExtra: RequestExtra
  ): Promise<CallToolErrorHookResult> {
    // Log the error
    console.error(`Error in tool ${originalRequest.params.name}:`, error);
    
    // Option 1: Transform the error
    throw new Error(`Custom error: ${error.message}`);
    
    // Option 2: Recover from the error
    return {
      resultType: 'respond',
      response: {
        content: [{ type: 'text', text: 'Recovered from error' }]
      }
    };
    
    // Option 3: Pass through unchanged
    return { resultType: 'continue' };
  }
}

Type Safety

All types are exported with Zod schemas for runtime validation:

import { 
  ToolCallRequestHookResultSchema,
  ToolCallResponseHookResultSchema 
} from '@civic/hook-common';

// Validate hook request results
const validatedRequestResult = ToolCallRequestHookResultSchema.parse(hookRequestResult);

// Validate hook response results
const validatedResponseResult = ToolCallResponseHookResultSchema.parse(hookResponseResult);

Examples

Logging Hook

export class LoggingHook extends AbstractHook {
  get name(): string {
    return 'logging-hook';
  }

  async processCallToolRequest(request: CallToolRequest): Promise<ToolCallRequestHookResult> {
    console.log(`[${new Date().toISOString()}] Tool called: ${toolCall.params.name}`);
    console.log('Arguments:', JSON.stringify(toolCall.params.arguments, null, 2));
    
    return {
      resultType: 'continue',
      request: toolCall
    };
  }

  async processCallToolResult(
    response: CallToolResult,
    originalCallToolRequest: CallToolRequest
  ): Promise<ToolCallResponseHookResult> {
    console.log(`[${new Date().toISOString()}] Response from: ${originalCallToolRequest.params.name}`);
    console.log('Response:', JSON.stringify(response, null, 2));
    
    return {
      resultType: 'continue',
      response: response
    };
  }
}

Validation Hook

export class ValidationHook extends AbstractHook {
  get name(): string {
    return 'validation-hook';
  }

  async processCallToolRequest(request: CallToolRequest): Promise<ToolCallRequestHookResult> {
    // Validate tool calls
    if (toolCall.params.name === 'dangerous-tool') {
      return {
        resultType: 'abort',
        reason: 'This tool is not allowed',
        body: null
      };
    }
    
    return {
      resultType: 'continue',
      request: toolCall
    };
  }

  async processCallToolResult(
    response: CallToolResult,
    originalCallToolRequest: CallToolRequest
  ): Promise<ToolCallResponseHookResult> {
    return {
      resultType: 'continue',
      response: response
    };
  }
}

API Reference

Types

  • Hook - Interface for implementing hooks
  • ToolCallRequestHookResult - Result type for request processing
  • ToolCallResponseHookResult - Result type for response processing
  • ListToolsRequestHookResult - Result type for tools list request processing
  • ListToolsResponseHookResult - Result type for tools list response processing
  • ToolCallTransportErrorHookResult - Result type for tool call transport error processing
  • ListToolsTransportErrorHookResult - Result type for tools list transport error processing
  • InitializeRequestHookResult - Result type for initialize request processing
  • InitializeResponseHookResult - Result type for initialize response processing
  • InitializeTransportErrorHookResult - Result type for initialize transport error processing
  • TransportError - Error type for transport-layer errors
  • CallToolRequest, CallToolResult, ListToolsRequest, ListToolsResult, InitializeRequest, InitializeResult - Re-exported from MCP SDK

Schemas

  • ToolCallRequestHookResultSchema - Zod schema for request hook result validation
  • ToolCallResponseHookResultSchema - Zod schema for response hook result validation
  • ListToolsRequestHookResultSchema - Zod schema for tools list request result validation
  • ListToolsResponseHookResultSchema - Zod schema for tools list response result validation
  • ToolCallTransportErrorHookResultSchema - Zod schema for tool call transport error validation
  • ListToolsTransportErrorHookResultSchema - Zod schema for tools list transport error validation
  • InitializeRequestHookResultSchema - Zod schema for initialize request validation
  • InitializeResponseHookResultSchema - Zod schema for initialize response validation
  • InitializeTransportErrorHookResultSchema - Zod schema for initialize transport error validation
  • TransportErrorSchema - Zod schema for transport error validation

Classes

  • AbstractHook - Abstract base class for implementing hooks with default pass-through implementations

Utilities

  • createHookRouter - Creates a tRPC router for hook implementation
  • createLocalHookClient - Creates a local client for a hook instance

License

MIT

Keywords

mcp

FAQs

Package last updated on 20 Dec 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts