npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@civic/hook-common

v0.9.0

Published

Common utilities and types for implementing MCP server hooks

Readme

@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

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

Hook Execution Flow

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

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

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

  4. 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