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

@sms-florin/mcpmeter

v0.2.0

Published

Drop-in usage metering and rate limiting for MCP servers. No database, no config — one function call per tool invocation.

Readme

mcpmeter

Drop-in usage metering and rate limiting for MCP servers. No database, no config file, no account to create — import it, call two functions, done.

import { Meter } from "@sms-florin/mcpmeter";

const meter = new Meter({ dailyLimit: 1000 });

// inside your tool handler, before doing the real work:
if (!meter.record("my_tool")) {
  return { error: "Daily limit reached, try again tomorrow." };
}

That's the whole quickstart. meter.stats() gives you today / last 7 days / last 30 days / total, broken down per tool name — enough to answer "is anyone actually using this?" without standing up analytics infrastructure.

Why this exists

Every MCP server that gets real traffic eventually needs two things it didn't ship with on day one: a way to see how much it's actually being called, and a way to stop a runaway caller (bug or abuse) from costing real money or locking up a shared resource. Building that twice — once as a quick hack, once properly after something breaks — is a waste of an afternoon. mcpmeter is that afternoon, done once, with the honest tradeoffs written down.

What it does NOT do

  • No persistence by default for rate limits. RateLimiter and DailyLimiter are in-memory — counts reset on process restart. If you're guarding something that costs real money per call, that's not enough on its own; pair it with your own persistent ceiling (a row in whatever database you already have). UsageStore's stats, by contrast, do support optional JSON-file persistence via PersistentUsageStoreRegistry, since losing a few hours of usage counts is low-stakes in a way losing a rate limit isn't.
  • No distributed/multi-process coordination. Each process has its own counters. Fine for a single hosted instance; not a fit for a horizontally-scaled fleet without your own shared store behind it.
  • No dashboard, no UI. stats() returns plain JSON — wire it into whatever you already use to look at logs/metrics, or expose it as its own MCP tool (get_usage_stats) the way the examples below do.

API

Meter — the 90% case

const meter = new Meter({ dailyLimit?: number });
meter.record(toolName: string, opts?: { client?, durationMs?, error? }): boolean   // false if dailyLimit exceeded
meter.wrap(toolName, fn, opts?): Promise   // times fn, records errors, throws past dailyLimit
meter.stats(): UsageStats

Latency, errors and per-client breakdown

record() takes an optional second argument; whatever you pass shows up under stats().detail for the last 7 and 30 days:

meter.record("my_tool", { client: "claude-desktop", durationMs: 120, error: false });

// or let mcpmeter time the handler and catch failures for you:
const result = await meter.wrap("my_tool", () => doWork(), { client: "claude-desktop" });

meter.stats().detail.last7Days;
// { tools:   { my_tool: { calls: 40, errors: 2, avgMs: 118, maxMs: 930 } },
//   clients: { "claude-desktop": 31, cursor: 9 } }

avgMs/maxMs are null for tools that never reported a duration. All optional: plain record(toolName) works exactly as before, and usage files written by older versions still load.

UsageStore / PersistentUsageStoreRegistry — usage counts only

Use these directly when you need per-caller breakdowns (multi-tenant hosted server) rather than one global counter.

import { PersistentUsageStoreRegistry } from "@sms-florin/mcpmeter";

const usage = new PersistentUsageStoreRegistry("./data/usage.json");
await usage.load(); // on startup

// per request, keyed by whatever identifies the caller (API key hash, etc.)
usage.record(callerKeyHash, toolName, { client, durationMs, error }); // last arg optional

// periodically (e.g. every 30s) — cheap no-op if nothing changed
await usage.save();

// expose as a tool:
usage.for(callerKeyHash).stats();

RateLimiter / DailyLimiter — limits only

import { RateLimiter, DailyLimiter } from "@sms-florin/mcpmeter";

const rl = new RateLimiter();
rl.check(bucketKey, max, windowMs): boolean
rl.remaining(bucketKey, max, windowMs): number

const daily = new DailyLimiter();
daily.checkGlobal(name, max): boolean                         // whole-service ceiling
daily.checkPerCaller(name, callerId, max, windowMs): boolean  // per-caller window

Call .sweep() periodically (e.g. hourly) on either to drop stale entries so memory doesn't grow unbounded in a long-running process.

Install

npm install @sms-florin/mcpmeter

Zero required dependencies. TypeScript types included.

License

MIT