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

@eliware/mcp-server

v1.1.10

Published

Pure-ESM Node.js MCP server with automatic tool discovery, Streamable HTTP and stdio transports, TLS, Zod schemas, injectable context, bearer authentication, and generic OAuth2 support.

Readme

eliware.org

@eliware/mcp-server npm version license build status

A minimal, pure-ESM MCP server for Node.js. Drop .mjs tools into an entrypoint-adjacent tools/ directory, start one entrypoint, and the library discovers and registers them automatically.

Features

  • Stateless Streamable HTTP by default.
  • Optional stdio transport for local MCP clients.
  • Optional stateful transport mode.
  • Automatic .mjs tool discovery.
  • Zod input schemas.
  • None, static bearer, bearer passthrough, and OAuth2 authentication modes.
  • Injectable application context for databases and services.
  • BigInt-safe buildResponse() helper.
  • Express HTTP integration.
  • HTTP, HTTPS, or both, with optional HTTP-to-HTTPS redirects.
  • TypeScript declarations.

Requirements

  • Node.js 26 or newer
  • An MCP client for live HTTP, HTTPS, or stdio operation

Install

npm install @eliware/mcp-server

Minimal server

// server.mjs
import { mcpServer } from '@eliware/mcp-server';

await mcpServer({
  auth: { mode: 'static', token: process.env.MCP_TOKEN },
});

Put tools in ./tools/. The MCP endpoint is /mcp only. There is no root or legacy compatibility endpoint.

See examples/basic.mjs for a complete entrypoint example. Put application tools in a tools/ folder beside that entrypoint.

Local stdio mode

For local clients that launch the server as a child process:

await mcpServer({ stdio: true });

stdio uses stdin/stdout for MCP JSON-RPC. Do not write logs to stdout; use stderr instead. HTTP remains the default for remote deployments.

Tool template

Create tools/hello.mjs beside your entrypoint:

import { z, buildResponse } from '@eliware/mcp-server';

export default async function registerHello({ mcpServer, toolName, log, db }) {
  mcpServer.tool(
    toolName,
    'Say hello.',
    { name: z.string().min(1) },
    async ({ name }) => {
      log.debug(`${toolName} request`, { name });
      // `db` and other values come from mcpServer({ context: { db } }).
      void db;
      return buildResponse({ message: `Hello, ${name}!` });
    },
  );
}

Tool requirements:

  1. File ends in .mjs.
  2. File exports one default async registration function.
  3. The function receives { mcpServer, toolName, log, ...context }.
  4. Call mcpServer.tool(name, description, inputSchema, handler).
  5. Define handler inputs with Zod (z.string(), z.number(), z.object(), etc.).
  6. Return an MCP result, normally using buildResponse(value).

Injected context

await mcpServer({
  context: { db, config, services },
});

Every tool receives those values as properties of its registration argument.

Configuration

mcpServer(options) supports:

  • auth: { mode: 'none' | 'static' | 'bearer-passthrough' | 'oauth2', ... }.
  • toolsDir: custom tool directory. Defaults to <entrypoint-directory>/tools/; when no entrypoint is available, uses the bundled examples/tools/ directory.
  • httpPort: HTTP listener port; set null/false to disable HTTP.
  • httpsPort: HTTPS listener port. Requires TLS key/certificate material or file paths.
  • tls: Node HTTPS TLS options (key, cert, optional ca) or file paths (keyFile, certFile, caFile). If omitted, TLS_KEY_FILE, TLS_CERT_FILE, and TLS_CA_FILE are used.
  • httpRedirect: when true, HTTP redirects to HTTPS instead of serving MCP.
  • context: values injected into every tool.
  • stateless: defaults to true; creates a fresh server/transport per request.
  • endpointPath: defaults to /mcp; use one explicit endpoint path per deployment.
  • enableJsonResponse: defaults to true.
  • allowedOrigins: optional CORS allowlist.
  • entrypoint: entrypoint path used to resolve the default sibling tools/ directory.
  • app: optional caller-supplied Express application. When omitted, the server creates one as before.
  • configureApp: optional callback invoked with the application after built-in HTTP middleware is installed and before the MCP route.

Existing Express application

Embed MCP in an existing Express app by supplying app. Use configureApp for application-specific middleware or routes:

await mcpServer({
  app,
  configureApp: configuredApp => {
    configuredApp.use(requestIdMiddleware);
  },
});

Both options are optional; without them, mcpServer creates and configures its own app.

A complete REST-plus-MCP example is available in examples/express/server.mjs:

import express from 'express';
import { mcpServer } from '@eliware/mcp-server';

const app = express();
app.get('/health', (req, res) => res.json({ status: 'ok' }));

await mcpServer({
  app,
  httpPort: 1234,
  endpointPath: '/mcp',
  auth: { mode: 'static', token: process.env.MCP_TOKEN },
});

The resulting application serves both /health and /mcp from the same HTTP listener. Use configureApp when routes or middleware should be installed as part of server setup:

await mcpServer({
  app,
  configureApp: configuredApp => {
    configuredApp.use('/api', apiRoutes);
  },
});

API helpers

  • buildResponse(value): returns { content: [{ type: 'text', text }] }.
  • convertBigIntToString(value): recursively converts BigInts to strings.
  • z: re-exported Zod namespace.

TypeScript

Type declarations are included in index.d.ts.

Errors / Troubleshooting

The server exposes only /mcp for MCP traffic. Use stdio: true for local process clients and do not write logs to stdout in stdio mode. Authentication, TLS, tool discovery, and OAuth failures should be handled through the configured logger without exposing tokens, credentials, private keys, or sensitive tool data. Always close the returned server resources during shutdown.

Development

npm install
npm run lint
npm test
npm run test:gaps
npm run lint
npm run typecheck
npm run pack

Security

Keep MCP_TOKEN, OAuth credentials, TLS keys, certificates, and client secrets in environment variables or mounted secret files. Do not commit .env files or certificates. Use HTTPS and explicit authentication for remote deployments.

License

MIT © Eli Sterling, eliware.org

HTTPS

HTTPS only:

await mcpServer({
  httpPort: null,
  httpsPort: 443,
  tls: { key: process.env.TLS_KEY, cert: process.env.TLS_CERT },
});

Both listeners, redirecting HTTP to HTTPS:

await mcpServer({
  httpPort: 80,
  httpsPort: 443,
  httpRedirect: true,
  tls: { key: process.env.TLS_KEY, cert: process.env.TLS_CERT },
});

Set httpRedirect: false to serve MCP over both listeners. The returned result exposes httpInstance and httpsInstance.

Containers

Local HTTP test:

cp .env.example .env
# set MCP_TOKEN in .env
docker compose up --build

HTTPS automatically loads TLS files from tls.keyFile/certFile/caFile, or TLS_KEY_FILE/TLS_CERT_FILE/TLS_CA_FILE; mount certificates read-only and never commit them. docker-compose.tls.yml exposes ports 80 and 443 for that deployment pattern.

The container uses container.mjs so the local package source resolves correctly. Local stdio remains a process mode, not a Docker network service:

docker run --rm -i -e MCP_TOKEN=test ghcr.io/eliware/mcp-server node examples/basic.mjs --stdio

Authentication modes

Unauthenticated:

auth: { mode: 'none' }

Static token:

auth: { mode: 'static', token: process.env.MCP_TOKEN }

Bearer passthrough validates that a bearer exists and exposes it to tools through request metadata for backend API calls:

auth: { mode: 'bearer-passthrough' }

OAuth2 uses the same request-scoped auth context as the other modes. Tools should use requireAuth(extra) or requireBearer(extra) rather than reading raw request headers.

OAuth2 resource-server mode validates introspection results and injects sanitized request identity plus granted scopes into tool metadata. requiredScopes defines the minimum scopes a token must grant; the complete granted scope list remains available to tools:

auth: {
  mode: 'oauth2',
  issuer: 'https://auth.example',
  resource: 'https://app.example/mcp',
  requiredScopes: ['app:read'],
  introspect: token => introspectToken(token),
}

The introspection function is application-provided so the library does not hard-code an identity provider or persistence system. Static and dynamic OAuth client registration are available through the client-side helpers below.

OAuth client registration

The package exports provider discovery and dynamic registration helpers. Applications choose static credentials or persist dynamically registered credentials through an injected store; the library does not require a database.

const provider = await discoverOAuthProvider({ issuer });
const client = await registerOAuthClient({
  registrationEndpoint: provider.registration_endpoint,
  metadata: { client_name: 'my-app', redirect_uris: ['https://app.example/callback'] },
});

Use createClientStore({ load, save, remove }) with MySQL, Kubernetes secrets, or another durable store.

OAuth2 mode also publishes protected-resource metadata at:

  • /.well-known/oauth-protected-resource
  • /.well-known/oauth-protected-resource/mcp

Unauthorized OAuth2 responses include WWW-Authenticate resource metadata and required scopes.

For backward compatibility, scopes is accepted as an alias for requiredScopes, but new integrations should use requiredScopes.

Dynamic client registration is resolved once and cached through the injected client store. Static registration bypasses registration and returns configured credentials. The store must be durable when running multiple replicas.

For app-side OAuth login, createOAuthClient() combines provider discovery and static/dynamic client resolution, then provides PKCE authorization URL creation and authorization-code exchange. Applications remain responsible for state/verifier persistence and user sessions.

Auth helper exports:

  • requireScope(extra, scope) — throws a 403-style error when absent.
  • hasScope(extra, scope) — boolean scope check.
  • getUser(extra) — sanitized request identity.
  • getAccessToken(extra) — explicit backend passthrough accessor.

OAuth2 may use the generic introspection adapter with auth.introspection.endpoint and optional client credentials, or an injected auth.introspect function.

PKCE helpers provide S256 verifier/challenge generation and one-time state consumption. Applications should persist PKCE records through a durable store and delete them after callback validation.

Support

For help, questions, or community chat:

eliware.org on Discord

Links