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

@modelcontextprotocol/server-lazy-auth

v1.7.3

Published

MCP App example demonstrating lazy (on-demand) OAuth: public tools work unauthenticated, protected tools return 401 + WWW-Authenticate so the host runs the OAuth flow only when needed

Readme

Example: Lazy Auth Server

An MCP App example demonstrating lazy (on-demand) auth: the server connects and lists tools without any authentication, and only asks for OAuth when a protected tool is actually called — by answering 401 with a WWW-Authenticate header. A public MCP App renders an "Auth me" button; clicking it calls a protected tool via callServerTool. The host sees the 401, runs the OAuth flow, retries, and the result renders inline.

The embedded OAuth authorization server is a deliberately minimal mock (HS256 JWTs, stateless auth codes, auto-approve consent page) so the whole flow runs from a single process with no external dependencies. It is not a production authorization server.

Tools

| Tool | Auth | Description | | ------------------- | ------------- | ----------------------------------------------------------------------------------------------------- | | show_auth_button | public | Renders buttons: "Auth me" (calls get_secret), "Revoke token" (calls revoke_auth_token) | | get_secret | protected | Returns secret data (requires Bearer token) | | revoke_auth_token | protected | Revokes the caller's entire auth session (access + refresh token) → forces full re-auth | | elicit_url | public | URL elicitation via elicitInput (blocks until the elicitation completes) | | elicit_by_error | public | URL elicitation via the -32042 (UrlElicitationRequired) error; succeeds on retry after completion |

Getting Started

npm install
npm start
# → MCP endpoint at http://localhost:3097/mcp

To test with a remote MCP host, expose the server through a public tunnel (see Testing MCP Apps) and set PUBLIC_URL to the tunnel URL so OAuth metadata and callback URLs use it:

PUBLIC_URL=https://<your-tunnel-host> npm start

This example is HTTP-only (no stdio mode): the lazy-auth flow relies on HTTP status codes and OAuth endpoints.

Environment Variables

| Var | Required | Description | | --------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | JWT_SECRET | recommended | 32+ byte secret for HS256 signing (openssl rand -hex 32). A dev-only default is used if unset. | | PORT | no | Local port (default 3097) | | PUBLIC_URL | non-local | Public base URL of the server (e.g. your tunnel URL). Required for non-localhost deployments; client-supplied Host headers are only trusted for loopback hosts | | ACCESS_TOKEN_TTL_SECONDS | no | Default access-token lifetime (default 30, short on purpose so you can watch the host's refresh flow kick in) | | REFRESH_TOKEN_TTL_SECONDS | no | Default refresh-token lifetime (default 300). Between the access and refresh TTLs, calls succeed via silent refresh; past it, a full re-auth is required | | REACTIVE_AUTH_ONLY | no | Set 1 to remove auth metadata from the root /.well-known/oauth-* paths so hosts can't discover auth preemptively — discovery then only happens via the 401 |

Per-connection token lifetimes

The short defaults are great for watching the refresh flow, but slow or automated clients may want tokens that survive a whole session. Any client can request a different access-token lifetime by connecting to a TTL-scoped MCP endpoint path (capped at 24 hours):

https://<host>/ttl/3600/mcp     ← tokens for this connection live 1 hour

This works through RFC 8707 resource indicators: MCP hosts send the MCP server URL as the resource parameter in OAuth authorization and token requests, and this server issues tokens for that grant with the lifetime encoded in the path (refresh tokens are extended to at least match). The TTL is a path segment rather than a query param because hosts canonicalize resource indicators and strip query strings. Each TTL endpoint also enforces its value as a maximum token age, so connecting to a path with a lower TTL than a token's issued lifetime forces the refresh flow. To exercise the full re-auth flow, call the revoke_auth_token tool.

How It Works

  1. Connect without authinitialize, tools/list, and public tool calls succeed with no Authorization header.
  2. Protected tool → 401 — when get_secret or revoke_auth_token is called without a (valid) Bearer token, the server responds 401 with WWW-Authenticate: Bearer resource_metadata="…/auth/prm".
  3. Discovery — the host follows resource_metadata to the protected-resource metadata (RFC 9728), which points at the authorization server metadata (RFC 8414).
  4. OAuth flow — the host runs the authorization-code + PKCE flow against the mock /authorize and /token endpoints (a small consent page keeps the popup visible).
  5. Retry — the host retries the tool call with the Bearer token and the secret renders inline in the app.
  6. Refresh + revocation — access tokens expire after 30 seconds and refresh tokens after 5 minutes by default, so all three states are easy to observe: direct success (<30s), silent refresh (30s–5min), and full re-auth (>5min). Connections can request different lifetimes via the /ttl/<seconds>/mcp endpoint path (see Per-connection token lifetimes), and revoke_auth_token invalidates the whole session immediately.

The two elicit_* tools demonstrate the complementary pattern of URL elicitation, where the server asks the user to open a URL (e.g. to complete sign-in) either by blocking inside the tool call (elicit_url) or by failing with the -32042 error and succeeding on retry (elicit_by_error).

Architecture

  • Stateless auth codes — grant details are encoded inside the authorization code as a 5-minute JWT, so nothing needs to be stored between requests.
  • Short-lived tokens — access tokens default to a 30 second TTL and refresh tokens to 5 minutes: first get_secret succeeds → wait >30s → next call 401s → host refreshes → retry succeeds → wait >5min → full re-auth. Per-connection overrides via the /ttl/<seconds>/mcp endpoint path.
  • HS256 — a single shared secret; no key-pair persistence.
  • Per-request MCP server — each /mcp request gets a fresh McpServer + StreamableHTTPServerTransport (stateless, no session IDs).
  • Session revocation — all tokens from one OAuth session share a sid claim; revoke_auth_token adds the sid to an in-memory revocation list checked by both token verification and the refresh grant.

Key Files