Skip to main content

Overview

import { HookRegistry } from '@frontmcp/sdk';

// Access via scope
const hooks = scope.hooks;

// Get all hooks
const allHooks = hooks.getHooks();

// Get hooks for a specific flow
const toolHooks = hooks.getFlowHooks('tools:call-tool');

Methods

getHooks()

Get all hooks (instances, unordered).
getHooks(): ReadonlyArray<HookEntry>
Example:
const hooks = registry.getHooks();
for (const hook of hooks) {
  console.log(`Hook: ${hook.flow} @ ${hook.stage}`);
}

getFlowHooks()

Get hooks for a given flow, sorted by priority (descending).
getFlowHooks(flow: string): ReadonlyArray<HookEntry>
Example:
const hooks = registry.getFlowHooks('tools:call-tool');
// Hooks are sorted: highest priority first

getFlowStageHooks()

Get hooks for a specific flow and stage.
getFlowStageHooks(flow: string, stage: string): ReadonlyArray<HookEntry>
Example:
const beforeHooks = registry.getFlowStageHooks('tools:call-tool', 'beforeExecute');
const afterHooks = registry.getFlowStageHooks('tools:call-tool', 'afterExecute');

getFlowHooksForOwner()

Get flow hooks filtered by owner.
getFlowHooksForOwner(flow: string, ownerId?: string): ReadonlyArray<HookEntry>
Example:
// Get hooks only for 'my-app' owner
const appHooks = registry.getFlowHooksForOwner('tools:call-tool', 'my-app');

getClsHooks()

Get hooks defined on a given class.
getClsHooks(token: Token): ReadonlyArray<HookEntry>
Example:
const classHooks = registry.getClsHooks(MyToolClass);

registerHooks()

Register hooks dynamically.
registerHooks(embedded: boolean, ...records: HookRecord[]): void
ParameterTypeDescription
embeddedbooleanWhether hooks are class-embedded
recordsHookRecord[]Hook definitions to register
Example:
registry.registerHooks(false, {
  flow: 'tools:call-tool',
  stage: 'beforeExecute',
  priority: 100,
  handler: async (ctx) => {
    console.log('Tool called:', ctx.toolName);
  },
});

Hook Structure

interface HookEntry {
  flow: string;           // Flow name (e.g., 'tools:call-tool')
  stage: string;          // Stage name (e.g., 'beforeExecute')
  priority: number;       // Execution priority (higher = first)
  handler: HookHandler;   // Handler function
  ownerId?: string;       // Owner filter
  embedded: boolean;      // Whether embedded in a class
}

Available Flows

tools:call-tool

Tool execution flow

resources:read-resource

Resource read flow

resources:list-resources

Resource listing flow

prompts:get-prompt

Prompt retrieval flow

Flow Stages

Most flows have these stages:
StageDescription
beforeValidationBefore input validation
afterValidationAfter input validation
beforeExecuteBefore execution
afterExecuteAfter execution
onErrorOn error (for error handling)
finallyAlways runs (cleanup)

Priority

Hooks are sorted by priority (descending):
// Priority 100 runs before priority 50
registry.registerHooks(false,
  { flow: 'tools:call-tool', stage: 'beforeExecute', priority: 100, handler: firstHook },
  { flow: 'tools:call-tool', stage: 'beforeExecute', priority: 50, handler: secondHook },
);

Embedded vs External Hooks

Embedded Hooks

Defined on tool/resource/prompt classes using @Hooks decorator:
@Tool({ name: 'my_tool', inputSchema: {} })
@Hooks([
  {
    stage: 'beforeExecute',
    handler: (ctx) => console.log('Before execute'),
  },
])
class MyTool extends ToolContext {
  async execute() { }
}
Embedded hooks:
  • Are scoped to their class
  • Use getClsHooks() for lookup
  • Have embedded: true

External Hooks

Registered via plugins or directly:
@Plugin({
  name: 'audit',
  hooks: [
    {
      flow: 'tools:call-tool',
      stage: 'afterExecute',
      handler: async (ctx) => {
        await auditLog(ctx.toolName, ctx.result);
      },
    },
  ],
})
class AuditPlugin { }
External hooks:
  • Can target any flow
  • Use getFlowHooks() for lookup
  • Have embedded: false

Hook Context

Hooks receive a context object with:
interface HookContext {
  flow: string;
  stage: string;
  state: Map<string, unknown>;    // Shared state between hooks
  input?: unknown;                 // Input data
  output?: unknown;                // Output data (after execute)
  error?: Error;                   // Error (in onError stage)
  scope: Scope;                    // Access to registries
  skip(): void;                    // Skip remaining hooks
  abort(error: Error): void;       // Abort with error
}

Index Structure

Unlike other registries, HookRegistry uses specialized indexes:
IndexKeyDescription
recordsByClsTokenHistorical records by class
entriesByClsTokenHook instances by class
hooksByFlowflowHooks indexed by flow
hooksByFlowStageflow:stageHooks indexed by flow + stage