Skip to content

Latest commit

 

History

History
177 lines (135 loc) · 6.17 KB

File metadata and controls

177 lines (135 loc) · 6.17 KB

Quickstart

In about five minutes: install toolpass, ask it a question, put it in front of an agent tool, then run the same thing as a server. You need Python 3.10 or later; step 3 also uses Docker (or nothing more than the same pip install) and, for the Node client, Node 18.17 or later. Nothing here touches a real system; step 4 connects one.

1. Install and ask

python3 -m venv .venv && . .venv/bin/activate
pip install toolpass
curl -sO https://raw.githubusercontent.com/roee-hersh/toolpass/main/examples/toolpass.yaml

The example config has a demo connection that talks to nothing. In it, admin@example.com may write and dana@example.com may not. The engine runs in your process:

python3 -c '
from toolpass import Toolpass
tp = Toolpass.from_config("toolpass.yaml")
print(tp.check("dana@example.com", "demo", "thing.write", "thing:1"))
'
Decision(decision='deny', reason='denied: dana@example.com is not an admin', status=200)

Change the user to admin@example.com and the answer is allow. Try nobody@example.com and it is deny with user_not_found. Every answer is also a JSON line on stderr: that is the decision log, set by decision_log in the file.

The command line asks the same question without any code:

toolpass check -config toolpass.yaml -connection demo \
  -user dana@example.com -action thing.write -resource thing:1

2. Guard a tool

Wrap the function your agent calls, so it runs only after toolpass said allow. The user comes from your application, never from the model. Save as quickstart.py:

from contextvars import ContextVar
from toolpass import Toolpass, PermissionDenied, guarded

tp = Toolpass.from_config("toolpass.yaml")
current_user: ContextVar[str] = ContextVar("current_user")

@guarded(tp, "demo", "thing.write", "thing:{thing_id}", user=current_user)
def write_thing(thing_id: str) -> str:
    return f"wrote thing {thing_id}"  # your real action goes here

for who in ["admin@example.com", "dana@example.com"]:
    current_user.set(who)  # in a real agent: the signed-in user, never the model
    try:
        print(who, "->", write_thing(thing_id="1"))
    except PermissionDenied as e:
        print(who, "-> refused:", e.decision.reason)
python3 quickstart.py 2>/dev/null   # stderr carries the decision log
admin@example.com -> wrote thing 1
dana@example.com -> refused: denied: dana@example.com is not an admin

In a real agent, use your framework's adapter instead, configured once on the agent (toolpass.strands, toolpass.langchain, toolpass.mcp, ...), or put the framework's tool decorator on top of guarded. The agent guide covers each framework and where the user comes from.

3. Run it as a server

In-process, the agent's process holds toolpass's lookup credentials. To keep them out of it, run the same engine as a service and point the agent at it:

docker run --rm -p 8080:8080 -e TOOLPASS_API_KEY=change-me \
  -v "$PWD/toolpass.yaml:/etc/toolpass/toolpass.yaml:ro" ghcr.io/roee-hersh/toolpass

No Docker? The package you installed is the server too: TOOLPASS_API_KEY=change-me toolpass serve -config toolpass.yaml.

In a second terminal:

curl -X POST localhost:8080/check -H 'Authorization: Bearer change-me' \
  -d '{"user":"dana@example.com","connection":"demo","action":"thing.write","resource":"thing:1"}'
{"decision":"deny","reason":"denied: dana@example.com is not an admin"}

In quickstart.py, change one line and run it again; the output is the same:

tp = Toolpass.remote("http://localhost:8080", "change-me")  # or TOOLPASS_URL and TOOLPASS_API_KEY

Node talks to the server with toolpass-client. Save as quickstart.mjs (the .mjs extension lets it use import):

npm install toolpass-client
import { AsyncLocalStorage } from "node:async_hooks";
import { Toolpass, PermissionDenied, guarded } from "toolpass-client";

const tp = new Toolpass(); // reads TOOLPASS_URL and TOOLPASS_API_KEY
const currentUser = new AsyncLocalStorage();

const writeThing = guarded(tp, "demo", "thing.write", "thing:{thingId}", { user: currentUser })(
  async ({ thingId }) => `wrote thing ${thingId}`, // your real action goes here
);

for (const who of ["admin@example.com", "dana@example.com"]) {
  try {
    console.log(who, "->", await currentUser.run(who, () => writeThing({ thingId: "1" })));
  } catch (e) {
    if (!(e instanceof PermissionDenied)) throw e;
    console.log(who, "-> refused:", e.decision.reason);
  }
}
TOOLPASS_URL=http://localhost:8080 TOOLPASS_API_KEY=change-me node quickstart.mjs

4. Connect a real system

Pick the system your agent acts in, follow its integration page to create a read-only credential, and add a connection to toolpass.yaml. For example, Jira:

connections:
  - id: jira-main
    integration: jira
    url: https://acme.atlassian.net
    username: toolpass-bot@acme.com
    credential: env:JIRA_TOKEN

Then check the file and the credential before you rely on it:

export JIRA_TOKEN=...              # the token from the integration page
export TOOLPASS_API_KEY=change-me  # validate reads api_key when the file has one
toolpass validate -config toolpass.yaml
toolpass probe    -config toolpass.yaml -connection jira-main
toolpass check    -config toolpass.yaml -connection jira-main \
  -user you@acme.com -action DELETE_ISSUES -resource issue:PAY-123

toolpass catalog jira lists the actions you can ask about. The integrations that sign with a private key (a GitHub App, Google service accounts, Snowflake key pairs, Salesforce JWT, Microsoft 365 certificates) need pip install "toolpass[crypto]"; the Docker image has it.

Next

  • Architecture: how a check flows, what is cached, and the trust boundaries.
  • Deploy: in-process or as a server, with Docker, pip or Helm, and how to reach it over TLS.
  • Add toolpass to your agent: the adapters for Strands, LangChain and LangGraph, MCP, the OpenAI Agents SDK, the Claude Agent SDK, Google ADK, CrewAI, Pydantic AI, LlamaIndex, and the TypeScript client.