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

@matatbread/matbot-default-gate

v0.4.14

Published

matbot's default permission policy, seeded by the host rather than loaded as a plugin: asks about a privileged operation, offers to remember the answer, honours what was remembered — plus the built-in gate_action tool for inspecting and forgetting standin

Downloads

144

Readme

@matatbread/matbot-default-gate

matbot's default permission policy.

A privileged operation — installing a plugin, adding a provider profile, removing an MCP server, overwriting a tool another plugin owns — declares that acceptance is needed, by calling ctx.gate({ gate, subject, label, fallback }). What happens then is this package's business, and it is replaceable: an installation that wants different rules registers its own PermissionGate instead of undoing this one.

This is a library, not a plugin — do not list it in plugins:. Each host seeds it at boot (createDefaultGate as the PermissionGate, createGateTools() beside the plugin and provider builtins), because a minimal install's first act is a gated one: making the policy, or the means to inspect and undo an answer, conditional on a config line answers the question at the wrong moment.

This one reproduces matbot's historical behaviour:

  • ask the user, through whatever channel the turn has;
  • offer two standing answers — Always allow "" and Always allow every <gate>;
  • honour what was remembered, silently, next time;
  • with nobody to ask, answer the request's own fallback (which is what the call site says its non-interactive behaviour is — true for a tool-name collision at boot, false everywhere else).

Configuring it

Standing answers live in this package's own settings namespace, one key per gate id, so an installation authors them the ordinary way (the hosts exempt this one key from the "names no loaded plugin" warning, since nothing loads it):

default_settings:
  '@matatbread/matbot-default-gate':
    'tools.overwrite': [bash, plugin]    # subjects allowed without asking
    'plugin.add': true                   # every subject allowed — see the warning below

A value is true (allow every subject), or a list of subjects to allow. Anything else asks.

This is also the supported way to make a gated operation work for callers that have no prompt channel — invokeTool, a trigger, a compiled skill, POST /tools/:name — which otherwise get the call site's fallback (a refusal, for everything but tools.overwrite). Connecting an MCP server from a script is the usual case:

default_settings:
  '@matatbread/matbot-default-gate':
    'mcp_action.add': true

Naming the gate is the whole point of the seam: the alternative is editing the plugin that declares it, and an installation should not have to fork a tool to decide its own policy.

subject is the identifier the call site has, not a canonical identity. At plugin.add the plugin is not loaded yet, so the subject is the specifier as typed: an allow for @x/foo does not match https://…/foo.ts. That is correct — different trust root, different decision — but it means a policy keys on the spelling.

A gate that auto-approves plugin.add has granted everything. A loaded plugin has full Node capability and there is no in-process sandbox. And a standing answer is a decision, not a channel: it applies at every door, including POST /tools/:name, invokeTool and a trigger — so Always allow every plugin.add lets anything that can reach the HTTP endpoint install anything, with nobody asked. Prefer the per-subject form.

A policy can refuse where nobody is watching. decide(req, ask) gets ask === undefined when no human is reachable, so a policy that wants standing answers to apply only to interactive callers is four lines — if (ask === undefined) return req.fallback; before consulting them. It also runs under the ambient security principal, so tryCurrentPrincipal() says who is asking with no extra plumbing. Neither is what the shipped policy does: an installation that configures 'mcp_action.add': true precisely wants the non-interactive caller to succeed, so the default honours an answer wherever the operation happens. Write the stricter one if your deployment wants it.

These answers are not out of the model's reach. They live in .data/settings/, which bash can write and docker-bash mounts read-write, so on an install with a shell tool the model can author its own standing answer and every later prompt is skipped. This policy is therefore a record of decisions, not a security boundary; a deployment that needs one ships a gate with its rules compiled in — which is what the replaceable seam is for.

gate_action

  • { action: 'get' } — the standing answers in effect (optionally one gate). A stored answer and a configured default read identically, because the question is whether the prompt appears. A gate with no answer is not reported: on a fresh install this returns nothing, and every gate simply behaves as configured. There is no list of "all the gates" — ids are open, and an absent key says only that nobody answered, not what will happen. Name a gate to ask about one, which is also the only way to see one an installation configured but nobody has answered (settings cannot enumerate their keys).
  • { action: 'clear' } — forget standing answers so the operation asks again: everything, one gate, or one subject within a gate. Clearing means revert to the configured default, so an installation's own default_settings: floor survives it.

There is deliberately no set: the write path for a runtime actor is answering a prompt that names the specific act.

Gate ids

tools.overwrite (subject: tool name) · plugin.add / plugin.provision-deps / plugin.remove / plugin.npm-uninstall / plugin.load · provider.add / provider.add-unverified / provider.update / provider.update-unverified / provider.remove · mcp_action.add / mcp_action.remove.

A tool's ids are qualified with the name it is registered under, so one answer covers both runtimes' implementations of a tool. The list is open — a plugin this build never compiled against contributes ids, and an id this policy does not recognise is asked about, never allowed.

Two gates chain on the provider path (add-unverified → add, update-unverified → update): one user-visible operation can cost two decisions, the first carrying information the second does not.