Framework adapters
The adapters wrap a tool so the underlying function runs only when Vulnify allows the action you describe. They call guard(), so the function runs when finalDecision (Python: final_decision) is ALLOW, or when that field was omitted and decision is ALLOW. They are duck-typed. The Node.js package does not depend on LangChain, MCP, the OpenAI Agents SDK, or the Vercel AI SDK. The Python package does not import CrewAI or LangGraph.
You write a describe function that maps the tool arguments to a Vulnify action.
Node.js
Section titled “Node.js”Import from @vulnify/sdk.
| Function | Wraps | On block |
|---|---|---|
guardFunction(vulnify, describe, fn, wait?) |
Any async function. | Throws VulnifyBlockedError. |
guardedTools(vulnify, tools, wait?) |
A map of { describe, run }. Call tools.call(name, args). Unknown names throw. |
Throws. |
guardLangChainTool(vulnify, tool, describe, wait?) |
A tool with invoke and, if present, legacy call. |
Throws. Other properties are kept. |
guardMcpHandler(vulnify, describe, handler, wait?) |
The handler you pass to server.tool. |
Returns { isError: true, content: [{ type: 'text', text }] } instead of throwing. |
guardOpenAIAgentsTool(vulnify, tool, describe, opts?) |
A tool config with execute, or an existing function tool with invoke(runContext, input). |
Default onBlocked: 'result' returns the block message string to the model. 'throw' rethrows. |
guardAiSdkTools(vulnify, tools, describe, opts?) |
Tools passed to generateText / streamText. Only names listed in describe are guarded. |
Default returns { blocked: true, decision, reasons, eventId, message }. |
opts.wait is the same wait object as guard(). opts.onBlocked is 'result' or 'throw'.
OpenAI Agents SDK
Section titled “OpenAI Agents SDK”import { tool } from '@openai/agents';import { z } from 'zod';import { guardOpenAIAgentsTool, Vulnify } from '@vulnify/sdk';
const vulnify = new Vulnify({ apiKey: process.env.VULNIFY_API_KEY!, baseUrl: process.env.VULNIFY_BASE_URL, failMode: 'closed',});
const exportCustomers = tool( guardOpenAIAgentsTool( vulnify, { name: 'export_customers', description: 'Export customer records and email them to an address.', parameters: z.object({ rows: z.number().int(), email: z.string() }), execute: async ({ rows, email }) => `Exported ${rows} customers to ${email}`, }, ({ rows, email }) => ({ agent: 'SalesBot', action: 'EXPORT_DATA', resource: 'Customer Database', destination: email.endsWith('@yourcompany.com') ? 'INTERNAL' : 'EXTERNAL_EMAIL', recordsAffected: rows, }), { wait: { timeoutMs: 120_000 } }, ),);A blocked call returns the block message to the model. The execute function does not run. wait is optional. Without it, REVIEW is also returned to the model as not allowed.
Vercel AI SDK
Section titled “Vercel AI SDK”import { tool } from 'ai';import { z } from 'zod';import { guardAiSdkTools, Vulnify } from '@vulnify/sdk';
const vulnify = new Vulnify({ apiKey: process.env.VULNIFY_API_KEY!, baseUrl: process.env.VULNIFY_BASE_URL,});
const tools = guardAiSdkTools( vulnify, { deleteTicket: tool({ description: 'Delete a support ticket.', inputSchema: z.object({ ticketId: z.string() }), execute: async ({ ticketId }) => ({ deleted: ticketId }), }), searchDocs: tool({ description: 'Search the public documentation.', inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => ({ results: [`Docs about ${query}`] }), }), }, { deleteTicket: () => ({ agent: 'SupportBot', action: 'DELETE_DATA', resource: 'Support Tickets', recordsAffected: 1, }), },);searchDocs is not in describe, so it is returned unchanged. A blocked deleteTicket call resolves to { blocked: true, decision, reasons, eventId, message } and does not run execute.
LangChain and MCP
Section titled “LangChain and MCP”import { guardLangChainTool, guardMcpHandler } from '@vulnify/sdk';
const guarded = guardLangChainTool(vulnify, langchainTool, (input) => ({ agent: 'SalesBot', action: 'EXPORT_DATA', resource: 'Customer Database', recordsAffected: input.rows,}));
const handler = guardMcpHandler(vulnify, describeExport, async (args) => { return { content: [{ type: 'text', text: 'exported' }] };});guardMcpHandler is what you pass as the handler to server.tool(name, schema, handler).
guardedTools is the generic router for OpenAI-style or Anthropic-style tool calls when you are not using the adapters above:
const tools = guardedTools(vulnify, { export_customers: { describe: (args) => ({ agent: 'SalesBot', action: 'EXPORT_DATA', resource: 'Customer Database', recordsAffected: args.rows }), run: async (args) => exportCustomers(args), },});await tools.call(toolUse.name, toolUse.input);Python
Section titled “Python”Import from vulnify.adapters. describe returns the keyword arguments of Vulnify.check.
| Function | Wraps | On block |
|---|---|---|
guard_crewai_tool(vulnify, tool, describe, wait=None, on_blocked="message") |
A CrewAI BaseTool or @tool function, in place. Guards _run and _arun. |
"message" returns the reason string to the agent. "raise" raises VulnifyBlockedError. |
crewai_before_tool_call(vulnify, describe, wait=None) |
A CrewAI before_tool_call hook. Tools not in the map are left alone. |
Returns False, which tells CrewAI to skip the tool. The agent sees CrewAI’s generic blocked-by-hook text, not Vulnify’s reasons. |
langgraph_tool_guard(vulnify, describe, wait=None) |
ToolNode(..., wrap_tool_call=...), also usable as agent middleware. |
An error ToolMessage with Vulnify’s reasons. If LangChain is not installed, a plain dict. |
alanggraph_tool_guard(...) |
ToolNode(..., awrap_tool_call=...). The check runs in a worker thread. |
Same as the sync guard. |
CrewAI
Section titled “CrewAI”from vulnify import Vulnifyfrom vulnify.adapters import guard_crewai_tool
vulnify = Vulnify( api_key=os.environ["VULNIFY_API_KEY"], # Omit base_url to use https://api.vulnify.io, or VULNIFY_BASE_URL when that variable is set.)
def describe_export(args): email = str(args.get("email", "")) return { "agent": "SalesBot", "action": "EXPORT_DATA", "resource": "Customer Database", "destination": "INTERNAL" if email.endswith("@yourcompany.com") else "EXTERNAL_EMAIL", "records_affected": args.get("rows"), }
export_tool = guard_crewai_tool( vulnify, ExportCustomers(), describe_export, wait={"timeout": 120},)ExportCustomers is your BaseTool with _run(self, rows, email). The full sample is in the Python repository under examples/crewai_crew.py.
To guard every tool through a hook instead:
from crewai.hooks import register_before_tool_call_hookfrom vulnify.adapters import crewai_before_tool_call
register_before_tool_call_hook( crewai_before_tool_call(vulnify, {"export_customers": describe_export}))The hook is the right choice when you want one registration for the crew. Use guard_crewai_tool when the agent should read Vulnify’s reasons.
LangGraph
Section titled “LangGraph”from langgraph.prebuilt import ToolNodefrom vulnify.adapters import langgraph_tool_guard
guard = langgraph_tool_guard( vulnify, { "delete_ticket": lambda args: { "agent": "SupportBot", "action": "DELETE_DATA", "resource": "Support Tickets", "records_affected": 1, } },)
graph.add_node("tools", ToolNode(tools, wrap_tool_call=guard))Tools that are not in the map run unchanged. A blocked call becomes an error tool message and the tool does not run. For the async node, pass awrap_tool_call=alanggraph_tool_guard(...).

