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.

Using MCP servers?

If your agent's tools are exposed over MCP, use the MCP Gateway (drop-in proxy, no code changes) 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="vgt_...")

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.evaluate({
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.