Skip to main content

Authorizing Tool Calls without MCP

When an AI agent's tools are plain functions — a LangChain @tool, a LangGraph node, or your own custom agent loop — there's no MCP server to put a proxy in front of. You authorize each call by invoking the Vengtoo SDK inside the tool, before the tool does its work.

:::tip Using MCP servers? If your agent's tools are exposed over MCP, use the MCP Gateway (drop-in proxy, no code changes) or the MCP Adapter (embed in a server you own) instead. This guide is only for native function tools that don't go through MCP.

The deciding factor is whether the tools are exposed over MCP — not the language your agent is written in. :::

The pattern

Every tool call becomes an authorization check:

  • the agent is the subject,
  • the tool is the resource,
  • the action is invoke,
  • and the tool's arguments go into resource.properties so policies can inspect them.

Run the tool only if the decision is allow.

Unlike the Gateway and Adapter — which capture tool arguments automatically — here you pass the arguments into resource.properties yourself. That's the one bit of wiring the SDK path requires.

Install

pip install vengtoo

A reusable guard

Wrap the check once, then call it from any tool:

from vengtoo import Vengtoo, Subject, Resource, Action

# Cloud, or point at a local agent with base_url="http://localhost:8181"
client = Vengtoo(api_key="azx_...")

def authorize_tool(agent_id: str, tool_name: str, args: dict) -> None:
"""Raise if the agent may not invoke this tool with these arguments."""
allowed = client.check(
subject=Subject(id=agent_id, type="ai_agent"),
action=Action(name="invoke"),
resource=Resource(id=tool_name, type="tool", properties=args),
)
if not allowed:
raise PermissionError(
f"{agent_id} is not allowed to call {tool_name} with {args}"
)

Using it in a LangChain tool

from langchain_core.tools import tool

@tool
def issue_refund(customer_id: str, amount: float) -> str:
"""Refund a customer."""
authorize_tool(
"agent:support-bot",
"issue_refund",
{"customer_id": customer_id, "amount": amount}, # → resource.properties
)
return payments.refund(customer_id, amount) # only runs if authorized

The same shape works for a LangGraph node or a hand-rolled agent loop — call authorize_tool(...) at the top of the function and let the exception short-circuit a denied call.

Argument-level policies

Because the arguments land in resource.properties, your policy can decide on the values, not just the tool. For example, an ABAC rule:

support_rep can invoke issue_refund only if amount < 100

evaluates resource.properties.amount on every call. A refund of 250 is denied before payments.refund ever runs; a refund of 50 goes through. See ABAC conditions for the policy side.

Other languages

The pattern is identical in any SDK — it follows your agent's language, not the framework:

// Node / TypeScript — npm install @vengtoo/sdk
const { decision } = await vengtoo.authorize({
subject: { id: "agent:support-bot", type: "ai_agent" },
resource: { id: "issue_refund", type: "tool", properties: { customer_id, amount } },
action: { name: "invoke" },
});
if (!decision) throw new Error("not authorized");
// Go — go get github.com/vengtoo/vengtoo-go
allowed, _ := client.Check(ctx,
vengtoo.Subject{ID: "agent:support-bot", Type: "ai_agent"},
vengtoo.Action{Name: "invoke"},
vengtoo.Resource{ID: "issue_refund", Type: "tool", Properties: map[string]any{
"customer_id": customerID, "amount": amount,
}},
)

Next steps

  • MCP Gateway — If your tools are MCP servers, gate them with no code changes.
  • ABAC Conditions — Write rules over tool arguments.
  • SDKs & CLI — Full SDK reference for Python, Node, and Go.