npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xano-sdk/agents

v0.2.0

Published

Let people connect their AI agents to a Xano SDK workspace safely: scoped, expiring, revocable agent keys and an MCP server wrapper, plus approvals so a person OKs an agent's action before it runs. With Xano tests generated from your config, plus an optio

Readme

@xano-sdk/agents

Let people connect their AI agents to a Xano SDK workspace safely, and approve what those agents do. Two parts, which work together or alone:

  • Agent access (defineAgentAccess): each agent gets its own key in its own auth table. Keys are:

    • scoped (read, or read and write);
    • expiring (1 to 90 days);
    • revocable, checked on every call;
    • logged, with every call recorded.

    agent.tool() wraps each MCP tool in those checks, and agent.serve() builds the MCP server. A person's sign-in token never works on the MCP server, and an agent key never works on the human API.

    Optionally, sign-in for the clients that can't send a key (ChatGPT, Claude on the web and desktop): with @xano-sdk/mcp-oauth, a second server where the person signs in with their own account. The same tools, logged and revocable the same way.

  • Approvals (defineApprovals): an agent asks instead of acting. A person sees the request in plain words, may change one field, and approves or declines. Only an approval runs the action, as the approver, exactly once. It works for MCP agents and for the in-app assistant (@xano-sdk/chatbot).

Both ship Xano tests generated from your config. Pair with @xano-sdk/auth for the user table and @xano-sdk/rbac for permissions.

npm install @xano/sdk @xano-sdk/auth @xano-sdk/agents

Agent access

// xano/agent.ts
import { defineAgentAccess } from "@xano-sdk/agents";
import { userTable } from "@xano-sdk/auth";

export const agent = defineAgentAccess({
  user: userTable,
  server: { name: "notes-mcp", instructions: "Notes for the person who connected you. Every tool acts as them." },
});

// xano/mcp.ts: a tool. `conn` (the key) and `person` (id, name, role) are bound; act on conn.user_id.
export const listNotes = agent.tool({
  name: "list_notes", title: "List my notes", access: "read",
  description: "The connecting person's notes.",
  stack: [s.db.query({ table: note, where: expr(col("owner_id"), "=", ref("conn.user_id")), as: "rows" })],
  response: ref("rows"),
});
agent.serve({ tools: [listNotes /* , … */] });   // before register

agent.tool() adds the same checks to every tool:

  • the key exists and isn't revoked or expired;
  • the person behind it still exists;
  • write tools also need a read-and-write key;
  • the call stamps the key's last use and appends an activity row;
  • MCP annotations are set from access.

These checks run inline as the tool's first statements, so a tool that asks the person to confirm (s.mcp.elicit) still asks before anything writes.

| Adds | | |---|---| | tables | agent_connection (the auth table for keys), agent_activity | | functions | agent/connect, agent/check_key, agent/revoke | | endpoints (group agent, history off) | POST/GET agent/connections, POST agent/connections/{id}/revoke, GET agent/activity, GET agent/info | | MCP server | server.name, every tool authenticated with agent_connection |

Sign-in for ChatGPT and Claude (optional)

ChatGPT and Claude on the web and desktop can't send a header, so they can't use a key. They connect with OAuth: the person signs in with their own account and chooses Allow. @xano-sdk/mcp-oauth is that sign-in page. Pass its oauth block and agent access builds a second server for these clients:

import { registerMcpOauth } from "@xano-sdk/mcp-oauth";

export const mcpOauth = registerMcpOauth(app, { authTable: userTable, brandName: "Notes" });
export const agent = defineAgentAccess({
  user: userTable,
  server: { name: "notes-mcp", instructions: "…" },
  signIn: { oauth: mcpOauth.oauth },   // adds the notes-mcp-signin server
});
  • The same tools, as twins. agent.serve gives each tool a twin on the sign-in server, named <name>_signin (a workspace can't hold two tools with one name). Its stack binds the same conn and person, so you write each tool once.
  • One row per person. conn is the person's "Signed-in agents" row in agent_connection (kind: "sign-in"), made on their first call. Activity, approvals and check_approval work as for a key.
  • Revoking signs out. Revoking that row (POST agent/connections/{id}/revoke, "Sign out" on the Agents page) ends every sign-in the person gave the server. The platform refuses their tokens from that moment. Signing in again brings the row back.
  • Prompts and resources stay on the key server.
  • Pass every tool through agent.tool. agent.serve refuses a plain tool(), which would have no checks around it. approvals.checkTool("mcp") is built for it.
  • Setup is mcp-oauth's:
    1. Deploy with --allow-empty-env=MCP_OAUTH_LOGIN_URL.
    2. Set it to <backend URL>/api:<mcp-oauth canonical>/mcp_oauth/login with xanosdk env set.
    3. Put the same line in xano/.env, so later deploys keep it.
  • Hosted sign-in needs Xano's cloud. A local Xano engine registers the server but doesn't run hosted sign-in. Test it on an ephemeral (deploy --ephemeral).

With sign-in on, GET agent/info answers oauth: true and signin_canonical.

Approvals

// xano/approvals.ts
import { defineApprovals } from "@xano-sdk/agents";

export const approvals = defineApprovals({
  user: userTable,
  actions: {
    send_reply:   { fn: sendReply,   label: "Send reply",   editable: "body" },
    issue_credit: { fn: issueCredit, label: "Issue credit", editable: "amount", approvers: ["admin"] },
  },
});

// in a tool: ask instead of acting
approvals.request({ ...agent.asker, action: "send_reply", title: …, preview: inp("body"), payload: obj({ … }) })
// let the agent check the outcome
agent.serve({ tools: [replyTool, approvals.checkTool("mcp")] });

Each action is four settings:

  • fn: what runs. Its inputs come from the payload by name. It also gets actor_id (the approver) and requester_id if it declares them, and it should re-check the approver's permissions.
  • label: the approve button.
  • editable: one field the person may change first. A number must stay a number: "abc" is refused, not turned into 0.
  • approvers: roles that approve instead of the requester (with @xano-sdk/rbac: rbac.rolesWith("credits.issue")). Anyone with the role approves, never the person who asked.

What it guarantees:

  • Asking writes only the request.
  • It runs once: the request is locked inside a transaction while it's decided.
  • A failing action leaves the request waiting.
  • The edited value is what runs.
  • Only the right person decides; everyone else gets 404.
  • Decided stays decided.
  • Requests expire after 7 days.

With @xano-sdk/chatbot, add approvals.checkTool("assistant") to its tools, and add APPROVALS_PROMPT_RULE to its system prompt so the assistant says a draft is waiting.

| Adds | | |---|---| | table | approval | | functions | approvals/request, approvals/apply_edit, approvals/decide | | endpoints (group approvals) | GET approvals?view=pending\|decided, POST approvals/{id}/approve, POST approvals/{id}/decline |

The frontend (optional)

The backend works on its own, so build any UI you like against the endpoints above. @xano-sdk/agents/react is the reference: complete Connect an agent and Approvals screens. Use them as is, or read them as the example for your own.

npm install react react-dom   # React is an optional peer: only needed for this entry
import { createAgentsClient, AgentsProvider, AgentsPage, ApprovalsPage, usePendingCount } from "@xano-sdk/agents/react";

const agentsClient = createAgentsClient({
  agentApiBaseUrl: `${XANO_HOST}/api:agent`,          // your backend + /api:<canonical> of each group
  approvalsApiBaseUrl: `${XANO_HOST}/api:approvals`,  // (routes.gen.ts or your xano.lock has them)
  getToken: () => session.token,                       // the person's token, never an agent key
});

<AgentsProvider client={agentsClient} currentUserId={me.id} backendUrl={XANO_HOST}
  renderLink={(href, children) => <Link to={href}>{children}</Link>}>
  <Route path="/agents" element={<AgentsPage appName="Notes" />} />
  <Route path="/approvals" element={<ApprovalsPage />} />
</AgentsProvider>

const waiting = usePendingCount();   // for a sidebar badge
  • AgentsPage:
    • signing in comes first wherever it's ready (agent/info reports signin_ready once MCP_OAUTH_LOGIN_URL is set): pick Claude, ChatGPT, Claude Code, Cursor or VS Code and follow the numbered steps, then sign in with your own account and choose Allow. No key to copy;
    • an agent key is the advanced option under it, and the only way for Codex, Windsurf and other clients (or before sign-in is set up): name, access and expiry, then the key shown once with one-click or one-command setup;
    • a live check that turns to "Connected" when the agent makes its first call;
    • what each tool does, and which ones ask first;
    • connected agents, with a revoke (or, for signed-in agents, a sign-out) that asks before acting;
    • the activity feed.
  • RecordApprovals (<RecordApprovals link={/notes/${id}} />): on a record's own page, what agents are waiting on for that record, with Approve / Edit / Decline right there. It matches each pending approval's link (set when the tool asked) and renders nothing when nothing waits.
  • decorateApprovalReply: pass it to @xano-sdk/chatbot/react's decorateReply. A reply that names an approval (approval_id: 12, as APPROVALS_PROMPT_RULE asks) shows that request's card under it, with Approve / Edit / Decline right in the chat. The id leaves the text.
  • AgentActivityFeed: for the overview, the latest agent calls ("Claude Code ran list_notes · 3 min ago"), with a link to the Agents page and an invitation to connect one when there are none.
  • ApprovalsPage / ApprovalCard:
    • each request in plain words: who asked, for whom, what will happen, and the exact text, or the number and the agent's reason;
    • Approve, Edit and Decline. A refusal from your action (a permission, a limit) is shown as given, and the request keeps waiting;
    • expired requests read as expired;
    • a Waiting / Decided view.
  • Self-contained. It imports nothing from your app, and uses native elements styled with shadcn/ui's theme tokens, so in a shadcn/Tailwind app it takes your theme and dark mode. With Tailwind v4: @source "../node_modules/@xano-sdk/agents/dist";.
  • The contract. GET agent/info and GET approvals answer with contract. A frontend built for a different one shows an "update and redeploy" notice.
  • Types only, no backend. The React entry imports no SDK runtime. For your own UI, import AgentConnection, AgentActivity, Approval, ApprovalPage and the rest with import type from the root.

Register

import { registerAgents } from "@xano-sdk/agents";
registerAgents(app, { access: agent, approvals });   // or agent.register(app); approvals.register(app);

Each part registers once per instance. Pass canonical to either factory to pin its API group's URL segment. Otherwise your project's xano.lock mints one, unique per instance.

Tests that ship with it

register adds the module's own Xano tests, built from your config. Run them with npx xanosdk deploy --test or npx xanosdk test run-all:

  • Unit tests on approvals/apply_edit:

    • a number edit stays a number;
    • a word in a number field is refused;
    • text edits replace the text;
    • a blank edit, or a request with no editable field, changes nothing.

    On agent/check_key: a missing key is refused.

  • Workflow tests:

    • keys are made within bounds;
    • a read key can't write;
    • revoked, expired and orphaned keys stop at once, and only the owner revokes;
    • with sign-in on, a person's signed-in calls share one row, made on first use;
    • asking only records a request;
    • requests expire;
    • each self-approved action is decided once, by the person;
    • each routed action is decided by a role holder, never the requester.

They make their own people in an empty database, filling only the columns your user table has.

Marketplace

xanosdk init --marketplace @xano-sdk/agents can't write the call for you: only you know your tools and actions. It leaves a commented call and points here.

License

MIT