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

toolgate

v1.1.1

Published

Optimistic execution middleware for autonomous agents — let reads pass, intercept actions, approve at the end. Supports MCP servers and custom local/hosted tools.

Readme

ToolGate

Execution middleware for autonomous agents — intercept, approve, execute.

Every tool call your agent makes goes through ToolGate. Reads pass through instantly. Writes, sends, deletes, and executes are intercepted, return a phantom response so the agent keeps running, and queue for human approval. Approved actions execute for real — even after the agent process has stopped.

Agent calls tool ──▶ ToolGate classifies ──▶ READ?   ──▶ Execute immediately, record result
                                           │
                                           └──▶ ACTION? ──▶ Return phantom, store params
                                                              ┊
                                           Agent finishes ──▶ Session in dashboard
                                                              ┊
                                           From anywhere  ──▶ Approve / Edit / Reject
                                                              (executes for real)

Works with MCP servers, custom local tool functions, and hosted tool endpoints.

Install

npm install toolgate

Two ways to use ToolGate

1 — MCP servers

Wrap any MCP server. ToolGate auto-discovers tools, classifies them, and intercepts actions.

import { mcpToolGate, autoConfigFromMCP } from "toolgate";

const config = await autoConfigFromMCP([
  { name: "gmail",  url: "https://gmail-mcp.example.com",  headers: { "Authorization": "Bearer ya29.xxx" } },
  { name: "github", url: "https://github-mcp.example.com", headers: { "X-API-Key": "ghp_xxx" } },
]);

const gate = mcpToolGate(mcpExecutor, { apiKey: "tg_live_...", agentName: "inbox-agent", ...config });

gate.describe("Read inbox and draft replies to urgent emails");

const emails = await gate.proxy("gmail_listMessages", { maxResults: 10 }); // ✅ READ — runs immediately
const draft  = await gate.proxy("gmail_sendMessage", { to: "...", body: "..." }); // 🛑 SEND — intercepted

await gate.finalize();
// Session appears in dashboard. Click Approve & Execute to send the real email.

2 — Custom tools

No MCP server needed. Define your own tools as plain async functions.

import { ToolGate } from "toolgate";

const gate = new ToolGate(executor, { apiKey: "tg_live_..." });

// Wrap a map of functions — returns proxied versions to give your agent
const tools = gate.wrapTools({
  readFile:   async (p) => fs.readFile(p.path, "utf8"),    // auto-classified as READ
  writeFile:  async (p) => fs.writeFile(p.path, p.content), // auto-classified as ACTION
  deleteFile: async (p) => fs.unlink(p.path),              // auto-classified as ACTION
}, {
  readFile: { kind: "read", description: "Read a file from disk" }, // force read
});

// Give `tools` to your agent — ToolGate handles classification and interception
const content = await tools.readFile({ path: "config.json" });  // ✅ executes immediately
await tools.writeFile({ path: "out.txt", content: "hello" });   // 🛑 intercepted → approval

await gate.finalize();
await gate.approveAll(); // or let the dashboard handle it

You can also pass tools directly in config:

import { ToolGate, ToolDef } from "toolgate";

const gate = new ToolGate(executor, {
  apiKey: "tg_live_...",
  tools: [
    { name: "readFile",  description: "Read a file from disk",  kind: "read",   fn: readFileFn },
    { name: "writeFile", description: "Write content to a file",               fn: writeFileFn },
    { name: "deleteFile",description: "Delete a file",                          fn: deleteFileFn },
  ],
});

Mixing MCP + custom tools

Both work in the same gate instance:

const gate = new ToolGate(mcpExecutor, {
  apiKey: "tg_live_...",
  mcpServers: [{ name: "gmail", url: "...", tools: [...] }],
  tools: [
    { name: "readFile", description: "Read a local file", kind: "read", fn: readFileFn },
  ],
});

Local vs Hosted

| | Local (free) | Hosted (paid) | |---|---|---| | Custom tools | Provide fn — called in-process after approval | Provide endpoint — dashboard POSTs to your URL after approval | | MCP servers | Local / stdio MCP | ToolGate hosted MCP proxy | | Agent must be running for execution? | Yes | No — deferred execution, agent can be offline |

Hosted custom tool endpoint

const gate = new ToolGate(executor, {
  apiKey: "tg_live_...",
  tools: [
    {
      name: "sendReport",
      description: "Email a weekly summary report",
      endpoint: "https://myapp.com/hooks/send_report",
      headers: { "Authorization": "Bearer secret" },
    },
  ],
});

When the human approves in the dashboard, ToolGate POSTs to your endpoint:

POST https://myapp.com/hooks/send_report
Content-Type: application/json
Authorization: Bearer secret

{ "tool": "sendReport", "params": { ... } }

No agent process needs to be running. The result is saved back to the session.


Classification

ToolGate automatically classifies every tool call as a read (passthrough) or action (intercept) by analyzing the tool name and description:

  • list*, get*, search*, fetch*, read*read (passthrough)
  • send*, create*, delete*, update*, write*, post*action (intercepted)
  • Unknown → intercepted (safe default)

Override classification:

// In ToolDef
{ name: "myTool", description: "...", kind: "read" }   // always passthrough
{ name: "myTool", description: "...", kind: "action" } // always intercepted

// In config
const gate = new ToolGate(executor, {
  readTools:   ["alwaysPassThrough"],
  actionTools: ["alwaysIntercept"],
  classifier:  (name, params) => ({ intent: "write", isPassthrough: false, confidence: 1, reason: "custom" }),
});

Getting Your API Key

  1. Sign up at toolgate.dev
  2. Go to API Keys+ New Key
  3. Copy the key — no database setup required

Programmatic Approval

If the agent is still running and you want to handle approval in-process:

await gate.finalize();

// Approve everything
await gate.approveAll();

// Or per-action
await gate.executeApproval({
  sessionId: gate.sessionId,
  approvedAt: Date.now(),
  decisions: new Map([
    ["action-id-1", { action: "approve" }],
    ["action-id-2", { action: "reject" }],
    ["action-id-3", { action: "edit", actionId: "action-id-3", newParams: { to: "[email protected]" } }],
  ]),
});

Inspection

gate.reads;          // all reads that executed (with results)
gate.pending;        // all intercepted actions
gate.currentSession; // full session snapshot
gate.summary();      // pretty-printed CLI summary — shows [local-tool] / [hosted-tool] / [local-mcp] / [hosted-mcp]

API Reference

new ToolGate(executor, config)

| Config field | Type | Description | |-------------------|------------------------------------------------|-------------| | apiKey | string | ToolGate API key — required for dashboard sync | | agentName | string | Display name shown in dashboard | | tools | ToolDef[] | Custom tool definitions (local or hosted) | | mcpServers | MCPServerDef[] | MCP server definitions | | readTools | string[] | Tool names that always passthrough | | actionTools | string[] | Tool names that always intercept | | classifier | (name, params) => ToolClassification | Custom classifier override | | phantomResponse | (tool: ToolCall) => unknown | Custom phantom response factory |

ToolDef

| Field | Type | Description | |---------------|--------------------------------------------|-------------| | name | string | Must match what your agent calls | | description | string | Used by the classifier heuristic | | kind | "read" \| "action" | Force classification (omit to auto-classify) | | fn | (params) => Promise<unknown> | Local: called in-process after approval (free) | | endpoint | string | Hosted: dashboard POSTs here after approval (paid) | | headers | Record<string, string> | Auth headers for hosted endpoint |

MCPServerDef

| Field | Type | Description | |-----------|--------------------------|-------------| | name | string | Display name | | url | string | MCP server HTTP endpoint | | tools | MCPToolDef[] | Tool list (omit to use autoConfigFromMCP) | | headers | Record<string, string> | Auth headers sent on every call |

mcpToolGate(executor, config)

Convenience factory — equivalent to new ToolGate(executor, config). Useful when working exclusively with MCP servers.

autoConfigFromMCP(servers)

Calls tools/list on each server and returns a ToolGateConfig. Accepts headers per server.

Instance methods

| Method | Description | |-----------------------------|-------------| | .proxy | Executor to hand to your agent | | .wrapTools(fns, overrides?) | Wrap a { name: fn } map — returns proxied versions | | .describe(text) | Set a task description shown in the dashboard | | .finalize() | End session, persist to dashboard | | .approveAll() | Approve + execute all pending actions locally | | .rejectAll() | Reject all pending actions | | .executeApproval(result) | Execute with per-action decisions | | .summary() | Pretty-printed session summary |


License

MIT