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

@casys/mcp-server

v0.17.5

Published

Production-ready MCP server framework with concurrency control, auth, and observability

Readme

@casys/mcp-server

npm JSR License: MIT

The "Hono for MCP" — a production-grade framework for building Model Context Protocol servers in TypeScript.

Composable middleware, OAuth2 auth, dual transport, observability, and everything you need to ship reliable MCP servers. Built on the official @modelcontextprotocol/sdk.

rate-limit → auth → custom middleware → scope-check → validation → backpressure → handler

Why @casys/mcp-server?

The official SDK gives you the protocol. This framework gives you the production stack.

| | Official SDK | @casys/mcp-server | | ----------------------- | :----------: | :----------------------------: | | MCP protocol compliance | Yes | Yes | | Concurrency control | -- | 3 backpressure strategies | | Middleware pipeline | -- | Composable onion model | | OAuth2 / JWT auth | -- | Built-in + 4 OIDC presets | | Rate limiting | -- | Sliding window, per-client | | Schema validation | -- | JSON Schema (ajv) | | Streamable HTTP + SSE | Manual | Built-in session management | | OpenTelemetry tracing | -- | Automatic spans per tool call | | Prometheus metrics | -- | /metrics endpoint | | MCP Apps (UI resources) | Manual | registerResource() + ui:// | | Sampling bridge | -- | Bidirectional LLM delegation |


Install

# Deno (primary target — JSR)
deno add jsr:@casys/mcp-server

# Node (secondary — npm, via build-node compilation)
npm install @casys/mcp-server

Runtime targets

@casys/mcp-server is Deno-first. The canonical deployment path is Deno 2.x running on Deno Deploy or self-hosted Deno, with a Node 20+ distribution as a secondary target via scripts/build-node.sh (which swaps the HTTP runtime adapter and remaps @std/* imports to their npm equivalents).

| Runtime | Status | | --------------------------------------------- | :--------------: | | Deno 2.x (Deno Deploy, self-hosted) | ✅ Primary | | Node.js 20+ (Express, Hono-on-Node, bare) | ✅ Secondary | | Cloudflare Workers / workerd | ❌ Not supported | | Browser / WebContainer | ❌ Not supported |

If you need to target Cloudflare Workers or the browser, use @modelcontextprotocol/server directly with its workerd / browser shims — that package focuses on the protocol and runtime portability, while @casys/mcp-server focuses on the production stack (auth, middleware, observability, multi-tenant, MCP Apps helpers) for Deno deployments.


Quick Start

STDIO Server (5 lines)

import { McpApp } from "@casys/mcp-server";

const server = new McpApp({ name: "my-server", version: "1.0.0" });

server.registerTool(
  {
    name: "greet",
    description: "Greet a user",
    inputSchema: {
      type: "object",
      properties: { name: { type: "string" } },
      required: ["name"],
    },
  },
  ({ name }) => `Hello, ${name}!`,
);

await server.start();

HTTP Server with Auth

import { createGoogleAuthProvider, McpApp } from "@casys/mcp-server";

const server = new McpApp({
  name: "my-api",
  version: "1.0.0",
  maxConcurrent: 10,
  backpressureStrategy: "queue",
  validateSchema: true,
  rateLimit: { maxRequests: 100, windowMs: 60_000 },
  auth: {
    provider: createGoogleAuthProvider({
      audience: "https://my-mcp.example.com",
      resource: "https://my-mcp.example.com",
    }),
  },
});

server.registerTool(
  {
    name: "query",
    description: "Query the database",
    inputSchema: {
      type: "object",
      properties: { sql: { type: "string" } },
    },
    requiredScopes: ["db:read"],
  },
  async ({ sql }) => ({ rows: [] }),
);

await server.startHttp({ port: 3000 });
// GET  /health   → { status: "ok" }
// GET  /metrics  → Prometheus text format
// POST /mcp      → JSON-RPC (tools/call, tools/list, ...)
// GET  /mcp      → SSE stream (server→client notifications)

Secure-by-default HTTP options:

await server.startHttp({
  port: 3000,
  requireAuth: true, // fail fast if auth isn't configured
  corsOrigins: ["https://app.example.com"],
  maxBodyBytes: 1_000_000, // 1 MB
  ipRateLimit: { maxRequests: 120, windowMs: 60_000 },
});

Notes:

  • requireAuth: true throws if no auth provider is configured
  • corsOrigins defaults to "*" — use an allowlist in production
  • maxBodyBytes defaults to 1 MB (set null to disable)
  • ipRateLimit keys on client IP by default

Features

Middleware Pipeline

Composable onion model — same mental model as Hono, Koa, or Express.

import type { Middleware } from "@casys/mcp-server";

const timing: Middleware = async (ctx, next) => {
  const start = performance.now();
  const result = await next();
  console.log(
    `${ctx.toolName} took ${(performance.now() - start).toFixed(0)}ms`,
  );
  return result;
};

server.use(timing);

Built-in pipeline: rate-limit → auth → custom → scope-check → validation → backpressure → handler

OAuth2 / JWT Auth

Four OIDC presets out of the box:

import {
  createAuth0AuthProvider, // Auth0
  createGitHubAuthProvider, // GitHub Actions OIDC
  createGoogleAuthProvider, // Google OIDC
  createOIDCAuthProvider, // Generic OIDC (Keycloak, Okta, etc.)
} from "@casys/mcp-server";

const auth0 = createAuth0AuthProvider({
  domain: "my-tenant.auth0.com",
  audience: "https://my-mcp.example.com",
  resource: "https://my-mcp.example.com",
  scopesSupported: ["read", "write"],
});

Or use JwtAuthProvider directly for custom setups:

import { JwtAuthProvider } from "@casys/mcp-server";

const provider = new JwtAuthProvider({
  issuer: "https://my-idp.example.com",
  audience: "https://my-mcp.example.com",
  resource: "https://my-mcp.example.com",
  authorizationServers: ["https://my-idp.example.com"],
});

Token verification is cached (SHA-256 hash → AuthInfo, TTL = min(token expiry, 5min)) to avoid redundant JWKS round-trips.

YAML + Env Config

For binary distribution — users configure auth without code:

# mcp-server.yaml
auth:
  provider: auth0
  audience: https://my-mcp.example.com
  resource: https://my-mcp.example.com
  domain: my-tenant.auth0.com
  scopesSupported: [read, write, admin]

Env vars override YAML at deploy time:

MCP_AUTH_AUDIENCE=https://prod.example.com ./my-server --http --port 3000

Priority: programmatic > env vars > YAML > no auth

RFC 9728

When auth is configured, the framework automatically exposes GET /.well-known/oauth-protected-resource per RFC 9728.

Observability

Every tool call emits an OpenTelemetry span with rich attributes:

mcp.tool.call query
  mcp.tool.name       = "query"
  mcp.server.name     = "my-api"
  mcp.transport        = "http"
  mcp.session.id       = "a1b2c3..."
  mcp.tool.duration_ms = 42
  mcp.tool.success     = true

Enable with Deno's native OTEL support:

OTEL_DENO=true deno run --unstable-otel server.ts

The HTTP server exposes a Prometheus-compatible /metrics endpoint:

mcp_server_tool_calls_total 1024
mcp_server_tool_calls_success_total 1018
mcp_server_tool_calls_failed_total 6
mcp_server_tool_call_duration_ms_bucket{le="50"} 892
mcp_server_tool_call_duration_ms_bucket{le="100"} 987
mcp_server_tool_calls_by_name{tool="query",status="success"} 512
mcp_server_active_requests 3
mcp_server_active_sessions 42
mcp_server_sse_clients 7
mcp_server_uptime_seconds 86400

Programmatic access:

server.getServerMetrics(); // Full snapshot (counters, histograms, gauges)
server.getPrometheusMetrics(); // Prometheus text format string

Concurrency Control

Three backpressure strategies when the server is at capacity:

| Strategy | Behavior | | ----------------- | ------------------------------------------ | | sleep (default) | Busy-wait with configurable sleep interval | | queue | FIFO queue with ordered release | | reject | Fail fast with immediate error |

new McpApp({
  maxConcurrent: 10,
  backpressureStrategy: "queue",
});

Rate Limiting

Sliding window rate limiter with per-client tracking:

new McpApp({
  rateLimit: {
    maxRequests: 100,
    windowMs: 60_000,
    keyExtractor: (ctx) => ctx.args.clientId as string,
    onLimitExceeded: "wait", // or "reject"
  },
});

For HTTP endpoints, use startHttp({ ipRateLimit: ... }) to rate limit by client IP (or custom key).

Security Best Practices (Tool Handlers)

Tool handlers receive untrusted JSON input. Treat args as hostile:

  • Define strict schemas: additionalProperties: false, minLength, pattern, enum.
  • Never pass raw args to a shell (Deno.Command, child_process.exec). If you must, use an allowlist + argv array (no shell).
  • Validate paths & resources: allowlisted roots, deny .., restrict env access.
  • Prefer safe APIs: parameterized DB queries, SDK methods, typed clients.
  • Log sensitive actions: file writes, network calls, admin ops.

MCP Apps (UI Resources)

Register interactive UIs as MCP resources:

import { MCP_APP_MIME_TYPE, McpApp } from "@casys/mcp-server";

server.registerResource(
  { uri: "ui://my-server/viewer", name: "Data Viewer" },
  async (uri) => ({
    uri: uri.toString(),
    mimeType: MCP_APP_MIME_TYPE,
    text: "<html><body>...</body></html>",
  }),
);

Capability negotiation (clients that don't support MCP Apps)

Not every MCP client renders UI resources. Clients that do advertise the MCP Apps extension in their capabilities (per the SDK 1.29 extensions field). Read it from a tool handler to decide between rich UI and a text-only fallback:

import { MCP_APP_MIME_TYPE, McpApp } from "@casys/mcp-server";

const app = new McpApp({ name: "weather-server", version: "1.0.0" });

app.registerTool(
  {
    name: "get-weather",
    description: "Get the weather forecast for a city",
    inputSchema: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
    },
  },
  async ({ city }) => {
    const forecast = await fetchForecast(city);
    const cap = app.getClientMcpAppsCapability();

    if (cap?.mimeTypes?.includes(MCP_APP_MIME_TYPE)) {
      // Rich UI: small text summary + interactive resource
      return {
        content: [{ type: "text", text: `Forecast for ${city} loaded` }],
        _meta: { ui: { resourceUri: `ui://weather/${city}` } },
      };
    }

    // Text-only fallback for clients that can't render the UI
    return {
      content: [{ type: "text", text: formatForecastAsText(forecast) }],
    };
  },
);

getClientMcpAppsCapability() returns undefined before the client has completed its initialize handshake, when the client doesn't advertise MCP Apps support, or when the advertised capability is malformed. The standalone getMcpAppsCapability(clientCapabilities) function is also exported for use against arbitrary capability objects.

The constants MCP_APPS_EXTENSION_ID ("io.modelcontextprotocol/ui") and MCP_APPS_PROTOCOL_VERSION ("2026-01-26") are exported for agents that need to introspect the protocol target directly.


API Reference

McpApp

Note: ConcurrentMCPServer and ConcurrentServerOptions remain exported as @deprecated aliases for backwards compatibility and will be removed in v1.0. New code should use McpApp / McpAppOptions. The aliases point to the exact same class — instanceof checks pass on both.

const server = new McpApp(options: McpAppOptions);

// Registration (before start)
server.registerTool(tool, handler);
server.registerTools(tools, handlers);
server.registerResource(resource, handler);
server.registerResources(resources, handlers);
server.use(middleware);

// Transport
await server.start();                  // STDIO
await server.startHttp({ port: 3000 }); // HTTP + SSE
await server.stop();                    // Graceful shutdown

// Observability
server.getMetrics();              // { inFlight, queued }
server.getServerMetrics();        // Full snapshot
server.getPrometheusMetrics();    // Prometheus text format
server.getRateLimitMetrics();     // { keys, totalRequests }

// Introspection
server.getToolCount();
server.getToolNames();
server.getResourceCount();
server.getResourceUris();
server.getSSEClientCount();

// SSE (Streamable HTTP)
server.sendToSession(sessionId, message);
server.broadcastNotification(method, params);

Standalone Components

Each component works independently:

import { RateLimiter, RequestQueue, SchemaValidator } from "@casys/mcp-server";

// Rate limiter
const limiter = new RateLimiter({ maxRequests: 10, windowMs: 1000 });
if (limiter.checkLimit("client-123")) {
  /* proceed */
}

// Request queue
const queue = new RequestQueue({
  maxConcurrent: 5,
  strategy: "queue",
  sleepMs: 10,
});
await queue.acquire();
try {
  /* work */
} finally {
  queue.release();
}

// Schema validator
const validator = new SchemaValidator();
validator.addSchema("tool", {
  type: "object",
  properties: { n: { type: "number" } },
});
validator.validate("tool", { n: 5 }); // { valid: true, errors: [] }

HTTP Endpoints

When running with startHttp():

| Method | Path | Description | | ------ | --------------------------------------- | ----------------------------------------------------------- | | POST | /mcp or / | JSON-RPC endpoint (initialize, tools/call, tools/list, ...) | | GET | /mcp or / | SSE stream (server→client notifications) | | GET | /health | Health check | | GET | /metrics | Prometheus metrics | | GET | /.well-known/oauth-protected-resource | RFC 9728 metadata (when auth enabled) |


License

MIT