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

@maindala/agent-guard

v1.0.1

Published

Governance SDK for external agents — policy pre-flight, DLP redaction, and telemetry push

Readme

@maindala/agent-guard

CI

Governance SDK for external agents — policy pre-flight checks, DLP redaction, and telemetry push to the mAIndala governance plane. Opt-in: call the guard from your own agent's code before/after tool invocations to enforce your org's governance policy locally, without routing every tool call through a broker.

Install

npm install @maindala/agent-guard

Quick start

import { AgentGuard } from '@maindala/agent-guard';

const guard = new AgentGuard({ apiKey: process.env.MAINDALA_ORG_KEY }); // mx_... org gateway key

const decision = await guard.checkTool('send_email');
if (!decision.allow) {
  throw new Error(`Blocked by org policy: ${decision.reason}`);
}

const result = await sendEmail(/* ... */);
const redacted = guard.applyDlp(result, decision.dlpPatterns);

pushToolCallTelemetry(token, event) works standalone with zero org configuration — see @maindala/telemetry if that's all you need; this package is the fuller governance surface it sits alongside.

Fail-open vs. fail-closed — read this before deploying

checkTool() needs a default for one question: what happens when the mAIndala governance plane itself can't be reached — unreachable, erroring, returning something unparseable, or timing out? That's a failMode, not a policy decision from the plane, and as of 1.0.0 the default is 'closed': an unreachable plane denies. This is a breaking change from every 0.2.x release, which always failed open. See the CHANGELOG for the full rationale and the one-line migration if you're upgrading.

new AgentGuard({ apiKey })                          // failMode: 'closed' (default) — denies on outage
new AgentGuard({ apiKey, failMode: 'open' })         // reproduces 0.2.x exactly — allows on outage
new AgentGuard({
  apiKey,
  failMode: 'open',                                  // instance default: keep the agent running
  toolFailModes: { 'payments:refund': 'closed' },    // this one tool stays strict regardless
})

Choosing 'closed' (the default): an outage denies every tool call until the plane is back. Right for privileged, destructive, or regulated actions (payments, data deletion, anything a compliance owner would ask "was this checked?" about) — a governance blind spot must never look the same as "the request was actually reviewed and allowed."

Choosing 'open': an outage lets the agent keep working unchecked until the plane recovers. Right when availability matters more than enforcement for a given tool — read-only tools, low-risk actions, or an agent where a governance-plane outage genuinely should not be able to take the whole thing down. The security implication is the direct consequence of the choice and worth stating plainly: while the plane is unreachable, every 'open'-mode tool call is allowed through unchecked, for as long as the outage lasts.

Both modes share the same reliability machinery, all configurable:

  • timeoutMs (default 5000) — checkTool() aborts via a real AbortController rather than hanging forever on a gateway that accepts the connection and never responds. A timeout is treated as a guard error and routed through failMode like any other outcome, and is never retried — it has already spent its full time budget.
  • Bounded retry with jitter (maxRetries, default 2) on transport-level failures only — network errors, non-2xx status, unparseable JSON. A valid decision is never retried, even { allow: false }: a real policy denial always produces exactly one request.
  • A circuit breaker — after breakerThreshold (default 5) consecutive failed calls, every further call resolves immediately via failMode without touching the network, until breakerCooldownMs (default 30000) elapses and a single probe call tests recovery. This is what keeps a genuinely downed gateway from costing every tool call the full timeout.

The one case that fails neither open nor closed: calling checkTool() without config.apiKey throws immediately. That's a caller misconfiguration, not a governance-plane problem, so it's surfaced rather than silently resolved either way.

Observing decisions — onDecision

const guard = new AgentGuard({
  apiKey,
  onDecision(record) {
    // record: { toolRef, decision, reason, latencyMs, cacheHit }
    // decision: 'allow' | 'deny' | 'guard_error' | 'timeout'
    myAuditLog.write(record);
  },
});

onDecision fires for every checkTool() outcome, including cache hits (cacheHit: true) and plane-unreachable outcomes (decision: 'guard_error' | 'timeout'). This SDK does not persist decisions and is not itself an audit log — onDecision is the integration point for writing them into whatever audit system you already have. A callback that throws is caught and logged; it can never break the guarded call.

Governing a tool registry — wrapTool/wrapTools, and framework adapters

Calling checkTool() by hand at every call site works, but it's one forgotten call site away from being bypassed. wrapTool()/wrapTools() govern a tool once, at registration, instead:

const tools = [
  { name: 'send_email', execute: async (args) => sendEmail(args) },
  { name: 'read_crm',   execute: async (args) => readCrm(args) },
];

const governedTools = guard.wrapTools(tools);
// every governedTools[i].execute() now runs checkTool() + DLP redaction first, automatically

A denied call returns the denial to the caller as a normal string result — it never throws. The model needs to see "this was blocked by policy" as something it can reason about and report, not an unhandled crash:

await governedTools[0].execute({ to: '[email protected]', body: '...' });
// → '[Tool "send_email" was blocked by governance policy: rate_limit_exceeded]'

Coverage — make a forgotten wrapper visible, not silent

const report = guard.coverageReport(allRegisteredTools, wrappedTools);
report.ungoverned; // ['read_crm'] — exactly what was registered but never wrapped

Framework adapters

Two adapters ship as optional peer dependencies — the core @maindala/agent-guard import still has zero required runtime dependencies whether or not either framework is installed (only importing the adapter's own subpath needs the corresponding framework present):

// MCP TypeScript SDK — wrap the handler you'd pass to registerTool() anyway
import { wrapMcpTool } from '@maindala/agent-guard/adapters/mcp';

server.registerTool('send_email', config, wrapMcpTool(guard, 'send_email', async (args) => {
  return { content: [{ type: 'text', text: await sendEmail(args) }] };
}));
// Vercel AI SDK — wrap the whole tool() definition; every other field passes through unchanged
import { wrapVercelAiTool } from '@maindala/agent-guard/adapters/vercel-ai';

const sendEmailTool = wrapVercelAiTool(guard, 'send_email', tool({
  description: 'Send an email',
  inputSchema: z.object({ to: z.string(), body: z.string() }),
  execute:     async (args) => sendEmail(args),
}));

Only MCP SDK and Vercel AI SDK are covered (a Gate 1 scope decision) — LangChain/LangGraph and the OpenAI Agents SDK are explicitly out of scope. Runnable, fully offline examples for all of the above live in examples/ (npm run examples).

Metadata-only telemetry, by construction

pushToolCallTelemetry() never forwards more than the documented event shape (kind, toolName, target, latencyMs, decision, findingClasses) — the outbound request body is built by explicitly picking those fields, not by serializing whatever object you pass in. If you pass extra fields (accidentally or via a spread of a larger internal object), they're dropped before the request is sent and a warning is logged naming what was dropped, rather than being forwarded.

API

  • checkTool(toolRef): Promise<{ allow, reason, dlpPatterns }> — pre-flight policy check. Cached 30s per toolRef. Governed by failMode/toolFailModes, timeoutMs, retry, the circuit breaker, and onDecision — see the sections above.
  • applyDlp(text, patterns): string — apply DLP redaction patterns to a string.
  • checkAndRedact(toolRef, toolResult): Promise<{ allowed, reason, redacted }> — the two above, combined.
  • wrapTool(tool, options?): GovernableTool — returns a governed equivalent of tool; a denial returns as a normal string result, never a throw. See "Governing a tool registry" above.
  • wrapTools(tools, options?): GovernableTool[] — wrapTool() over a whole collection.
  • coverageReport(registered, wrapped): { totalRegistered, totalWrapped, ungoverned } — names registered-but-ungoverned tools. Pure; no network call.
  • pushTelemetry(usage): Promise<void> — push LLM usage/cost telemetry (requires apiKey + orgSlug). Never throws.
  • pushToolCallTelemetry(token, event): Promise<void> — push one metadata-only tool-call/A2A event to your free tail. Takes its own mt_ token; works with zero org config. Never throws.
  • @maindala/agent-guard/adapters/mcp → wrapMcpTool(guard, toolName, handler) — MCP SDK adapter.
  • @maindala/agent-guard/adapters/vercel-ai → wrapVercelAiTool(guard, toolName, toolDef) — Vercel AI SDK adapter.

Releasing

See RELEASING.md — publishing runs through a GitHub Release + trusted-publishing CI workflow with a required-reviewer approval gate, not a local npm publish.

License

MIT