Documentation Index Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
Use this file to discover all available pages before exploring further.
Hook Types
Decorator Timing Use Case @StageDefine execution stage Custom flow steps @WillBefore stage executes Pre-processing, validation @DidAfter stage executes Post-processing, logging @AroundWraps stage execution Caching, error handling
FlowHooksOf
Create typed hook decorators for a specific flow:
import { FlowHooksOf } from ' @frontmcp/sdk ' ;
const { Stage , Will , Did , Around } = FlowHooksOf ( ' tools:call-tool ' );
class MyHooks {
@ Will ( ' execute ' )
async beforeExecute ( ctx : ToolFlowContext ) {
console . log ( ' Before tool execution ' );
}
@ Did ( ' execute ' )
async afterExecute ( ctx : ToolFlowContext ) {
console . log ( ' After tool execution ' );
}
}
Available Flows
Flow Name Description tools:call-toolTool execution tools:list-toolsList tools resources:read-resourceRead resource resources:list-resourcesList resources prompts:get-promptGet prompt prompts:list-promptsList prompts http:requestHTTP request handling
@Will (Before Hook)
Execute before a flow stage:
const { Will } = FlowHooksOf ( ' tools:call-tool ' );
class ValidationHooks {
@ Will ( ' execute ' , { priority : 10 })
async validateInput ( ctx : ToolFlowContext ) {
const input = ctx . state . get ( ' input ' );
if (! input ) {
throw new Error ( ' Input is required ' );
}
}
@ Will ( ' execute ' , { priority : 20 })
async logStart ( ctx : ToolFlowContext ) {
console . log ( ` Starting tool: ${ ctx . state . get ( ' toolName ' ) } ` );
}
}
Priority
Lower priority values execute first:
@ Will ( ' execute ' , { priority : 10 }) // Executes first
@ Will ( ' execute ' , { priority : 20 }) // Executes second
Filter
Conditionally execute hooks:
@ Will ( ' execute ' , {
filter : ( ctx ) => ctx . state . get ( ' toolName ' ) === ' sensitive_tool '
})
async logSensitiveAccess ( ctx ) {
console . log ( ' Accessing sensitive tool ' );
}
@Did (After Hook)
Execute after a flow stage:
const { Did } = FlowHooksOf ( ' tools:call-tool ' );
class AuditHooks {
@ Did ( ' execute ' )
async auditToolCall ( ctx : ToolFlowContext ) {
const toolName = ctx . state . get ( ' toolName ' );
const output = ctx . state . get ( ' output ' );
await this . auditService . log ({
tool : toolName ,
timestamp : new Date (),
success : ! ctx . state . get ( ' error ' ),
});
}
}
@Around (Wrapper Hook)
Wrap stage execution for full control:
const { Around } = FlowHooksOf ( ' tools:call-tool ' );
class CachingHooks {
@ Around ( ' execute ' )
async cacheResults ( ctx : ToolFlowContext , next : () => Promise < void >) {
const cacheKey = this . getCacheKey ( ctx );
const cached = await this . cache . get ( cacheKey );
if ( cached ) {
ctx . state . set ( ' output ' , cached );
return ; // Skip actual execution
}
await next (); // Execute the stage
const output = ctx . state . get ( ' output ' );
await this . cache . set ( cacheKey , output );
}
}
@Stage (Custom Stage)
Define custom flow stages:
const { Stage , Will , Did } = FlowHooksOf ( ' tools:call-tool ' );
class CustomFlow {
@ Stage ( ' preProcess ' )
async preProcess ( ctx : ToolFlowContext ) {
// Custom pre-processing stage
const input = ctx . state . get ( ' input ' );
ctx . state . set ( ' processedInput ' , transform ( input ));
}
@ Will ( ' preProcess ' )
async beforePreProcess ( ctx ) {
console . log ( ' About to pre-process ' );
}
@ Did ( ' preProcess ' )
async afterPreProcess ( ctx ) {
console . log ( ' Pre-processing complete ' );
}
}
Hook Options
interface HookOptions < Ctx = unknown > {
priority ?: number ; // Execution order (lower = first)
filter ?: ( ctx : Ctx ) => boolean | Promise < boolean >; // Conditional execution
}
Registering Hooks
In Apps
@ App ({
name : ' my-app ' ,
providers : [ AuditHooks , CachingHooks ],
tools : [ MyTool ],
})
class MyApp {}
In Plugins
@ Plugin ({
name : ' audit-plugin ' ,
providers : [ AuditHooks ],
})
class AuditPlugin {}
Hook Context
Hooks receive flow context with access to:
interface FlowContext {
state : FlowState ; // Flow state (input, output, metadata)
logger : FrontMcpLogger ; // Logger instance
providers : ProviderRegistry ; // Dependency injection
}
// Access state
const input = ctx . state . get ( ' input ' );
ctx . state . set ( ' output ' , result );
// Access providers
const service = ctx . providers . get ( ServiceToken );
// Access logger
ctx . logger . info ( ' Processing ' );
Error Handling
Errors in hooks are caught and logged:
@ Will ( ' execute ' )
async validateInput ( ctx ) {
// Throwing stops execution and reports error
throw new InvalidInputError ( ' Validation failed ' );
}
@ Around ( ' execute ' )
async errorHandler ( ctx , next ) {
try {
await next ();
} catch ( error ) {
ctx . logger . error ( ' Execution failed ' , { error });
// Re-throw or handle
throw error ;
}
}
Full Example
import { FlowHooksOf , Provider , Plugin , App , FrontMcp , Tool , ToolContext } from ' @frontmcp/sdk ' ;
import { z } from ' @frontmcp/sdk ' ;
const { Will , Did , Around } = FlowHooksOf ( ' tools:call-tool ' );
// Audit hook provider
@ Provider ()
class AuditHooks {
private auditLog : Array <{ tool : string ; timestamp : Date ; duration : number }> = [];
@ Will ( ' execute ' , { priority : 0 })
async recordStart ( ctx ) {
ctx . state . set ( ' _auditStartTime ' , Date . now ());
}
@ Did ( ' execute ' , { priority : 1000 })
async recordEnd ( ctx ) {
const startTime = ctx . state . get ( ' _auditStartTime ' );
const duration = Date . now () - startTime ;
const toolName = ctx . state . get ( ' toolName ' );
this . auditLog . push ({
tool : toolName ,
timestamp : new Date (),
duration ,
});
ctx . logger . info ( ` Tool ${ toolName } completed in ${ duration } ms ` );
}
getAuditLog () {
return this . auditLog ;
}
}
// Rate limiting hook
@ Provider ()
class RateLimitHooks {
private calls = new Map < string , number []>();
private maxCallsPerMinute = 60 ;
@ Will ( ' execute ' , { priority : 5 })
async checkRateLimit ( ctx ) {
const toolName = ctx . state . get ( ' toolName ' );
const now = Date . now ();
const oneMinuteAgo = now - 60000 ;
const recentCalls = ( this . calls . get ( toolName ) || [])
. filter ( t => t > oneMinuteAgo );
if ( recentCalls . length >= this . maxCallsPerMinute ) {
throw new RateLimitError ( 60 );
}
recentCalls . push ( now );
this . calls . set ( toolName , recentCalls );
}
}
// Caching hook
@ Provider ()
class CacheHooks {
private cache = new Map < string , { value : unknown ; expires : number }>();
private ttl = 60000 ; // 1 minute
@ Around ( ' execute ' , {
filter : ( ctx ) => ctx . state . get ( ' toolMetadata ' )?. annotations ?. idempotentHint === true
})
async cacheIdempotent ( ctx , next ) {
const toolName = ctx . state . get ( ' toolName ' );
const input = ctx . state . get ( ' input ' );
const cacheKey = ` ${ toolName } : ${ JSON . stringify ( input ) } ` ;
const cached = this . cache . get ( cacheKey );
if ( cached && cached . expires > Date . now ()) {
ctx . logger . debug ( ' Cache hit ' , { toolName });
ctx . state . set ( ' output ' , cached . value );
return ;
}
await next ();
const output = ctx . state . get ( ' output ' );
this . cache . set ( cacheKey , {
value : output ,
expires : Date . now () + this . ttl ,
});
}
}
// Plugin bundling hooks
@ Plugin ({
name : ' observability ' ,
description : ' Audit logging, rate limiting, and caching ' ,
providers : [ AuditHooks , RateLimitHooks , CacheHooks ],
})
class ObservabilityPlugin {}
// Sample tool
@ Tool ({
name : ' get_data ' ,
inputSchema : { key : z . string () },
annotations : { idempotentHint : true },
})
class GetDataTool extends ToolContext {
async execute ( input : { key : string }) {
return { data : ` Value for ${ input . key } ` };
}
}
// App using plugin
@ App ({
name : ' data-app ' ,
plugins : [ ObservabilityPlugin ],
tools : [ GetDataTool ],
})
class DataApp {}
@ FrontMcp ({
info : { name : ' Observable Server ' , version : ' 1.0.0 ' },
apps : [ DataApp ],
})
export default class ObservableServer {}
HookRegistry Hook registry API
Flow Types Flow type definitions
Customize Flows Flow customization guide