Skip to main content
Unified events provide targeted observation without wrapping every request. They are best for focused events like auditing a single server, capturing diagnostics for specific tools, and observing session lifecycle. If you need to enforce policy for all tool calls, use Middleware.

When to use hooks

  • You only care about a single server or a small subset of tools.
  • You want audit logs without changing the control flow.
  • You want lightweight metrics without blocking requests.

Tool success event

proxy.on("tool:success", async ({ ctx, result, durationMs }) => {
  ctx.log.info("tool.success", { durationMs, isError: result?.isError });
});

Filtered event

proxy.on("tool:success", { server: "diagnostics" }, async ({ ctx }) => {
  ctx.log.info("diagnostics");
});

Server-scoped event

const github = proxy.mcp("github");

github.on("tool:error", async ({ ctx, error }) => {
  ctx.log.warn("github.tool.error", { message: error?.message });
});

Available filters

  • server: match the upstream server name.
  • tool: match the original tool name.
  • proxyTool: match the prefixed tool name.
Hooks run before middleware and can return a tool result to short-circuit the call. Legacy proxy.on("call", ...) hooks are still supported for compatibility. Use middleware or tool routes for runtime control, and use tool:start, tool:success, tool:error, tool:after, tools:list:after, session:start, and session:end for observation.

Use hooks for audits

proxy.on("tool:after", { proxyTool: "filesystem__delete" }, async ({ ctx, success }) => {
  ctx.log.warn("audit.delete", {
    userId: ctx.user.id,
    tool: ctx.tool?.proxyName,
    success,
  });
});

Use hooks for metrics

proxy.on("tool:success", async ({ ctx, durationMs }) => {
  ctx.log.info("metric.tool", {
    tool: ctx.tool?.proxyName,
    server: ctx.server?.name,
    durationMs,
  });
});

Combine hooks with middleware

Hooks can collect data while middleware enforces policy. This lets you capture metrics without blocking the request.
proxy.on("tool:success", async ({ ctx }) => {
  ctx.log.info("hook.metric", { tool: ctx.tool?.proxyName });
});

proxy.tool("filesystem.delete", async (ctx) => {
  if (ctx.user.role !== "admin") {
    return ctx.deny("Delete is disabled.");
  }
});

Debugging hooks

If a hook is not firing, verify the filter values and check the proxyToolName from logs. Most issues are due to mismatched prefixes.

Best practices

  • Use hooks for observation, middleware for control.
  • Keep hook handlers fast and non-blocking.
  • Prefer proxyTool filtering for clarity.
  • Avoid mutations in hooks; keep them side-effect free when possible.