Skip to main content
The Runlayer Hooks TypeScript SDK wires custom agent runtimes into Runlayer’s hook enforcement and telemetry pipeline. It is for agents that execute tools directly and need Runlayer to observe, rewrite, or block tool calls.
This SDK does not replace MCP client setup. To connect an agent framework to Runlayer-hosted MCP servers, still use the framework integration docs for Vercel AI SDK, OpenAI Agents SDK, or Google ADK. Use the adapters on this page for tools your TypeScript process executes locally.

Capabilities

  • lifecycle telemetry for session, prompt, and stop events
  • pre-tool enforcement with optional argument rewriting
  • post-tool output scanning and blocking
  • failed tool telemetry
  • direct MCP source enforcement
  • preflight session emission for ingestion checks
  • transcript-bearing Stop events for assistant reasoning extraction
  • Vercel AI SDK tool wrappers
  • OpenAI Agents SDK function tool wrappers
  • Google ADK FunctionTool option wrappers

Installation

pnpm add @runlayer/hooks-sdk@0.1.0
Install the framework package you use separately, such as @anthropic-ai/claude-agent-sdk, ai, @openai/agents, or @google/adk.

Configuration

Create a Runlayer user API key, then set:
export RUNLAYER_BASE_URL="https://your-runlayer-instance.com"
export RUNLAYER_API_KEY="rl_..."
To authenticate as an agent account instead, set:
export RUNLAYER_BASE_URL="https://your-runlayer-instance.com"
export RUNLAYER_AGENT_CLIENT_ID="client_..."
export RUNLAYER_AGENT_CLIENT_SECRET="..."
The SDK exchanges these credentials for a Runlayer bearer token with client_credentials, then caches and refreshes that token internally. Do not configure or pass a pre-minted bearer token. For OBO agent tokens, also set:
export RUNLAYER_AGENT_SUBJECT_TOKEN="user@example.com"
export RUNLAYER_AGENT_SUBJECT_TOKEN_TYPE="urn:runlayer:token-type:user-email"
The SDK defaults to client: "typescript-sdk" so events are attributed to the first-party Hooks TypeScript SDK in Runlayer. Enable the Hooks TypeScript SDK client in Runlayer workspace settings before testing. Optional environment variables:
VariablePurpose
RUNLAYER_AGENT_CLIENT_IDAgent account Client ID for client_credentials token exchange.
RUNLAYER_AGENT_CLIENT_SECRETAgent account Client Secret for client_credentials token exchange.
RUNLAYER_AGENT_SUBJECT_TOKENOptional user identity for OBO agent token exchange.
RUNLAYER_AGENT_SUBJECT_TOKEN_TYPEToken type for RUNLAYER_AGENT_SUBJECT_TOKEN; use urn:runlayer:token-type:user-id, urn:runlayer:token-type:user-email, or urn:ietf:params:oauth:token-type:access_token.
RUNLAYER_HOOK_CLIENTOverride the client name sent to Runlayer. Only use this when your deployment supports a dedicated SDK client name.
RUNLAYER_HOOK_DEBUG=1Log non-strict hook event failures to stderr.
RUNLAYER_HOOK_TIMEOUT_MSRequest timeout for Runlayer hook calls. Defaults to 10000.
RUNLAYER_HOOK_MAX_TOOL_OUTPUT_BYTESMaximum serialized tool output sent to Runlayer. Defaults to 65536; larger values are truncated with a marker.
RUNLAYER_HOOK_ENFORCEMENT_FAILURE_MODEEnforcement outage behavior. Defaults to closed; set to open only if your agent should continue when Runlayer cannot be reached.
RUNLAYER_ALLOW_INSECURE_TRANSPORT=1Allow a non-HTTPS RUNLAYER_BASE_URL for local development only.
RUNLAYER_DIRECT_MCP_SOURCE_ENFORCEMENT_PATHEndpoint path for direct MCP source validation, when enabled by your deployment.

Runtime Safety

Runlayer enforcement is fail-closed by default. If pre-tool, post-tool, or direct MCP source enforcement cannot reach Runlayer, the SDK treats the call as blocked. Lifecycle telemetry is best-effort unless a helper documents strict behavior, such as preflight and transcript Stop emission. Every hook request uses a timeout, and tool output is capped before upload. Failed tool outputs include bounded stdout, stderr, and output fields with truncation metadata when available, so Runlayer can scan failure context without the SDK uploading unbounded logs. The SDK skips pre-tool and post-tool enforcement for Runlayer’s own MCP server names (runlayer, runlayer-plugin, and onelayer) and for MCP calls whose toolUrl points at a Runlayer proxy URL. Third-party MCP tools remain enforced by default. For custom TypeScript runtimes, use the exported shouldEnforceTool(...) helper or the client’s toolEnforcement option so new adapters make the same decision consistently. Additional ignoredMcpServerNames are added to the default Runlayer self-MCP skip list; set skipRunlayerSelfMcp: false only if you need to enforce those self-MCP names. If you call shouldEnforceTool(...) directly with absolute self-hosted Runlayer MCP proxy URLs, pass runlayerBaseUrl; RunlayerClient does this automatically. baseUrl must use https:// unless allowInsecureTransport or RUNLAYER_ALLOW_INSECURE_TRANSPORT=1 is set. Only enable insecure transport for a trusted local development endpoint.

Preflight

Use preflight to verify that Runlayer receives hook events before invoking a real model.
import { RunlayerClient, sendRunlayerPreflight } from "@runlayer/hooks-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const result = await sendRunlayerPreflight(runlayer, {
  prompt: "Runlayer ingestion preflight",
});

console.log(result.sessionId);
Preflight sends a strict SessionStart, UserPromptSubmit, and Stop sequence for a synthetic session. If any request fails, the helper throws.

Claude Agent SDK Hooks

Pass Runlayer hooks into Claude Agent SDK’s query options.
import { query } from "@anthropic-ai/claude-agent-sdk";
import {
  claudeAgentSdkAssistantMessageToTranscriptLine,
  createClaudeAgentSdkHooks,
  emitClaudeAgentSdkTranscriptStop,
  RunlayerClient,
} from "@runlayer/hooks-sdk/claude-agent-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
  toolEnforcement: {
    ignoredMcpServerNames: ["internal-platform"],
  },
});

const transcriptLines: string[] = [];
let sessionId: string | undefined;

for await (const message of query({
  prompt: "Inspect this workspace and summarize the active project.",
  options: {
    allowedTools: ["Read", "Glob", "Grep", "Bash"],
    hooks: createClaudeAgentSdkHooks(runlayer, { includeStop: false }),
    maxTurns: 4,
    model: "claude-opus-4-6",
    thinking: { type: "adaptive" },
  },
})) {
  if (message.type === "system" && message.subtype === "init") {
    sessionId = message.session_id;
  }

  if (message.type === "assistant") {
    transcriptLines.push(
      claudeAgentSdkAssistantMessageToTranscriptLine(message.message),
    );
  }
}

if (sessionId) {
  await emitClaudeAgentSdkTranscriptStop(runlayer, {
    model: "claude-opus-4-6",
    sessionId,
    transcriptLines,
  });
}
Use includeStop: false when you emit a transcript-bearing Stop manually. That lets Runlayer extract assistant thinking or reasoning blocks from the completed transcript instead of receiving a bare lifecycle stop.

Tool Enforcement

If your runtime executes tools outside Claude Agent SDK hooks, wrap those calls with runTool.
import { RunlayerClient } from "@runlayer/hooks-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const output = await runlayer.runTool({
  execute: async (toolInput) => {
    return runLocalTool(toolInput);
  },
  sessionId: "session-id",
  toolInput: { command: "cat README.md" },
  toolName: "Bash",
  toolType: "shell",
});
Runlayer can return modified arguments from the pre-tool hook. The SDK passes those modified arguments to execute. If Runlayer denies the tool call or blocks the tool output, the SDK throws RunlayerBlockedError. When wrapping MCP tools manually, pass toolUrl if you have the MCP server URL. Runlayer MCP proxy URLs such as /api/v1/proxy/<id>/mcp, /api/v1/proxy/plugins/<id>/mcp, /api/v1/proxy/skills/<id>/mcp, and /api/v1/proxy/agent-account/<id>/mcp are skipped automatically because the proxy already runs MCP policy and scanner enforcement.

Vercel AI SDK Tools

Wrap a Vercel AI SDK tool set before passing it to generateText, streamText, or a ToolLoopAgent.
import { streamText, tool } from "ai";
import {
  RunlayerClient,
  withRunlayerVercelAiTools,
} from "@runlayer/hooks-sdk/vercel-ai-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const tools = withRunlayerVercelAiTools(
  {
    getWeather: tool({
      description: "Get weather for a city",
      inputSchema: {
        type: "object",
        properties: { city: { type: "string" } },
        required: ["city"],
      },
      execute: async ({ city }) => ({ forecast: `sunny in ${city}` }),
    }),
  },
  {
    client: runlayer,
    sessionId: "session-id",
  },
);

await streamText({
  model,
  prompt: "Check the weather in Paris",
  tools,
});
The adapter preserves the Vercel execution options and forwards toolCallId to Runlayer as toolUseId.

OpenAI Agents SDK Tools

Wrap function tool options before passing them to OpenAI Agents SDK’s tool(...) helper.
import { tool } from "@openai/agents";
import {
  RunlayerClient,
  withRunlayerOpenAIAgentsTool,
} from "@runlayer/hooks-sdk/openai-agents-sdk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const lookupTicket = tool(
  withRunlayerOpenAIAgentsTool(
    {
      name: "lookup_ticket",
      description: "Look up a support ticket",
      parameters: {
        type: "object",
        properties: { ticketId: { type: "string" } },
        required: ["ticketId"],
      },
      execute: async ({ ticketId }) => `ticket:${ticketId}`,
    },
    {
      client: runlayer,
      sessionId: "session-id",
    },
  ),
);
The adapter preserves context and details arguments and reads common toolCall IDs from details.

Google ADK Tools

Wrap Google ADK FunctionTool options before constructing the tool.
import { FunctionTool } from "@google/adk";
import {
  RunlayerClient,
  withRunlayerGoogleAdkTool,
} from "@runlayer/hooks-sdk/google-adk";

const runlayer = RunlayerClient.fromEnv({
  clientVersion: "my-agent/1.0.0",
});

const searchTickets = new FunctionTool(
  withRunlayerGoogleAdkTool(
    {
      name: "search_tickets",
      description: "Search support tickets",
      parameters: {
        type: "object",
        properties: { query: { type: "string" } },
        required: ["query"],
      },
      execute: async ({ query }) => ({ query }),
    },
    {
      client: runlayer,
      sessionId: "session-id",
    },
  ),
);

Direct MCP Source Enforcement

Use direct MCP source enforcement when a custom agent can call MCP servers without going through the Runlayer proxy.
const runlayer = RunlayerClient.fromEnv({
  directMcpSourceEnforcementPath: "/api/v1/hooks/direct-mcp-source",
});

await runlayer.enforceMcpSource({
  generationId: "generation-id",
  sessionId: "session-id",
  toolName: "mcp__github__list_repos",
  url: "https://tenant.runlayer.com/api/v1/proxy/server-id/mcp",
});
If Runlayer denies the source, enforceMcpSource throws RunlayerBlockedError.

API Reference

RunlayerClient.fromEnv(options?)

Creates a client from RUNLAYER_BASE_URL plus one auth source: RUNLAYER_API_KEY or RUNLAYER_AGENT_CLIENT_ID / RUNLAYER_AGENT_CLIENT_SECRET. When agent credentials are configured, the SDK exchanges them at /api/v1/oauth/token, caches the bearer token, and refreshes before expiry. The SDK does not accept pre-minted bearer tokens. Options:
OptionDescription
clientClient name sent to Runlayer. Defaults to RUNLAYER_HOOK_CLIENT or typescript-sdk.
clientVersionVersion string for your agent runtime.
directMcpSourceEnforcementPathEndpoint path for direct MCP source enforcement.
timeoutMsPer-request timeout in milliseconds. Defaults to 10000.
maxToolOutputBytesMaximum serialized tool output bytes sent to Runlayer. Defaults to 65536.
enforcementFailureModeclosed blocks on enforcement outages; open allows on outages. Defaults to closed.
toolEnforcementTool filtering options shared by generic wrappers and framework adapters. Defaults to skipping Runlayer self-MCP server names and Runlayer MCP proxy URLs.
allowInsecureTransportAllows non-HTTPS baseUrl; local development only.

createClaudeAgentSdkHooks(client, options?)

Returns a Claude Agent SDK hook map with lifecycle, pre-tool, post-tool, and failed-tool hooks. Options:
OptionDescription
includeStopInclude a stock Stop lifecycle hook. Set this to false when sending a transcript-bearing Stop manually.

sendRunlayerPreflight(client, options?)

Sends a strict synthetic lifecycle sequence to confirm ingestion.

emitClaudeAgentSdkTranscriptStop(client, options)

Sends a strict Stop event with newline-delimited transcript lines.

runTool(options)

Runs a custom tool through Runlayer pre-tool and post-tool enforcement.

Adapter helpers

  • withRunlayerVercelAiTools(tools, options) wraps a Vercel AI SDK tool set.
  • withRunlayerOpenAIAgentsTool(toolOptions, options) wraps OpenAI Agents SDK function tool options before calling tool(...).
  • withRunlayerGoogleAdkTool(toolOptions, options) wraps Google ADK FunctionTool options before constructing the tool.

enforceMcpSource(options)

Validates a direct MCP source before execution.