Tools
Scoped Access with JWTs
Let a tool publish a result and share it through a link that reaches one Actor and expires.
A tool can publish its work to an Actor and hand out a short-lived token for it. Whoever opens the link can read that one Actor, and nothing else.
import { Type } from "@earendil-works/pi-ai";
import { defineTool } from "@earendil-works/pi-coding-agent";
import { pi } from "@rivet-dev/pi";
import { setup } from "rivetkit";
import { admin, report } from "./report";
const shareReport = defineTool({
name: "share_report",
label: "Share report",
description: "Publish a report and return a link anyone can open for the next hour.",
parameters: Type.Object({ title: Type.String(), body: Type.String() }),
async execute(_toolCallId, { title, body }) {
const handle = await admin.report.create([crypto.randomUUID()], { input: { title, body } });
const { token } = await handle.issueToken({ subject: "shared-report", expiresIn: 3600 });
const url = `https://app.example.com/reports/${await handle.resolve()}#${token}`;
return { content: [{ type: "text", text: `Shared at ${url}` }], details: { url } };
},
});
const agent = pi({ model: "anthropic/claude-opus-5-5", customTools: [shareReport] });
export const registry = setup({ use: { agent, report } });
import { actor, type Registry } from "rivetkit";
import { createClient } from "rivetkit/client";
type Report = { title: string; body: string };
export const report = actor({
createState: (_c, input: Report): Report => input,
actions: {
read: (c) => c.state,
},
});
export const admin = createClient<Registry<{ report: typeof report }>>();
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
const reportId = location.pathname.split("/").pop() ?? "";
const token = location.hash.slice(1);
const client = createClient<typeof registry>({
endpoint: "https://api.rivet.dev",
namespace: "production",
token,
});
const report = await client.report.getForId(reportId).read();
document.title = report.title;
document.body.textContent = report.body;
share_reportcreates areportActor for each report, with the report as its input. A token can call every action on its Actor, soreportonly hasread.- The tool runs in your backend, where its client has your Rivet token.
issueTokenmints a token for this one report that expires after an hour. - The token goes in the URL fragment, which browsers don’t send to your server.
viewer.tsruns in the browser and connects with the token. A request to any other Actor is rejected before it reaches your code.- The link is part of the tool result, so the model and connected clients see it, and the agent can pass it on.
Tokens can’t be revoked, so keep them short. See JWTs for permissions and expiration.