Tools and approvals
Besides its built-in tools, an agent can call your own tools: a toolset is one MCP server (kind: mcp) or a set of HTTP endpoints (kind: http) under one base URL. The model sees each tool as <toolset>__<tool>, for example github__create_issue.
Keys stay with orkestr
The agent's VM only knows tool names and schemas. When it calls a tool, the orkestr gateway adds the toolset's credential and makes the call server-side, then hands back the result. A credential is stored encrypted and is never shown again, not even to you.
Add a toolset
from orkestr import Toolset
docs = Toolset.create("docs", "mcp", "https://mcp.example.com/mcp")
print([t["name"] for t in docs["tools"]]) # discovered from the server
# A server that needs a key: orkestr keeps it, the agent never sees it.
github = Toolset.create(
"github", "mcp", "https://api.githubcopilot.com/mcp/",
auth_header_name="Authorization", auth_secret="Bearer " + token,
)From the CLI, pass the key by the name of an environment variable, so it never appears in a terminal history or a coding assistant's chat:
export GITHUB_TOKEN=... # in your own shell
orkestr agents tools add github --url https://api.githubcopilot.com/mcp/ --auth-env GITHUB_TOKEN
orkestr agents publish my-agent --tools github--auth-header (default Authorization) and --auth-scheme (default Bearer for that header) shape the value. For --kind http, describe the endpoints in a JSON file: --http-tools-file tools.json, a list of {name, description, method, path, input_schema}.
Toolsets are not versioned: rotating a key or turning a tool off applies to every version that uses the toolset. Which toolsets an agent may use is part of its version (toolset_ids).
Connectors (OAuth)
Many services run their own MCP server and sign you in with their own OAuth: Linear, Notion, Sentry, Atlassian and others. Add one as a toolset with auth_type: oauth and connect it once: orkestr finds the provider's sign-in, registers itself as a client, and gives you a link. You approve in your browser; orkestr keeps the tokens encrypted, refreshes them, and sends them only from its gateway. The agent's VM never sees a token.
orkestr agents tools catalog
orkestr agents tools add linear --catalog linear
orkestr agents tools connect linear # prints the link, waits for the approval
orkestr agents publish my-agent --tools linearfrom orkestr import Toolset
print([c["slug"] for c in Toolset.catalog()])
linear = Toolset.create("linear", "mcp", "https://mcp.linear.app/mcp",
auth_type="oauth", catalog_slug="linear")
print(Toolset.connect(linear["id"])) # a person opens this link and approvesAny other MCP server that uses OAuth works the same with --url URL --oauth. A few providers (Google, Microsoft, Slack) do not let clients register themselves: create an OAuth app there with the redirect URL https://api.orkestr.eu/api/agent-toolsets/oauth/callback and pass --client-id and --client-secret-env VAR.
If the provider stops accepting the tokens (you revoked access, or removed a scope), the toolset shows needs_reconnect and the agent gets a plain "reconnect it" tool error instead of a crash. Run connect again. orkestr agents tools disconnect NAME forgets the tokens and asks the provider to revoke them where it can.
A connector acts on a real account, so every tool its server does not mark read-only (the MCP readOnlyHint) starts as approval: always. Change that per tool below; your choice is kept when the tool list is refreshed.
Connections per end user
By default a connector has one connection, yours, and every run uses it. If your agent serves your own users and should act in their Linear or Jira, turn on oauth_per_end_user: each end user connects their own account, and a run for that end_user_id uses only theirs.
from orkestr import Toolset
# Once: each end user connects their own account.
Toolset.update(linear_id, oauth_per_end_user=True)
# In your app, when a user clicks "Connect Linear":
url = Toolset.connect(linear_id, end_user_id="cust-42",
return_url="https://app.example.com/settings/integrations")
# redirect the user to url
Toolset.connections(linear_id) # who is connected ("-" = you)
Toolset.disconnect_end_user(linear_id, "cust-42")Your app asks for a link with the user's end_user_id and sends them to it. After they approve, they land on your return_url (https) with ?orkestr_connected=1, or ?orkestr_error=... if they declined. Without a return_url they see a short "connected, you can close this tab" page. From the CLI: orkestr agents tools connect linear --end-user cust-42.
Turn tools off, or require approval
orkestr agents tools set github create_issue --approval always
orkestr agents tools set github delete_repo --disable
orkestr agents tools refresh github # re-read the MCP server's tool listOver the API this is the toolset's tool_overrides: {"create_issue": {"enabled": true, "approval": "always"}}.
Approving a call
When the model calls a tool marked approval: always, the run pauses: an invoke returns status: pending_approval with the tool and its arguments, and a stream ends with run.paused. Approve or deny from your app, the API, or the console (the agent page, and the workspace's "Waiting for you"). On approval orkestr makes the call and the run continues where it stopped. Approvals expire after 24 hours by default.
from orkestr import AgentRun
result = agent.invoke("Open an issue for the login bug")
if result.status == "pending_approval":
print(result.approval.tool, result.approval.arguments)
AgentRun.get(result.run_id).approve() # or .deny()agents:invoke; managing toolsets needs agents:write.