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

@bonniernews/mcp-server

v0.1.5

Published

Wrapper for creating MCP servers

Readme

@bonniernews/mcp-server

A custom wrapper for an MCP (Model Context Protocol) server using Express. This library facilitates the creation and management of tools that can be exposed via an HTTP API, with special support for Atom API schemas.

Installation

npm install @bonniernews/mcp-server

Publishing

This package is published to npm automatically via CI/CD when changes are merged to the main branch.

To publish a new version:

npm run bump

[NOT WORKING] The CI/CD pipeline will automatically publish the new version to npm.

Then run npm publish --access public

MCPServer Configuration Properties

| Property | Type | Description | Default Value | | :--------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | | app | Express.Application | An optional existing Express application instance. If provided, MCPServer will mount its routes on this app and will not add its own body parsing, correlation ID middleware, or healthcheck route. You are responsible for starting your app. | undefined | | path | string | The base path where the MCP routes will be mounted. Only applicable when app is provided. | "/mcp/" | | tools | Array<Tool \| DynamicTool> | An array of tool definitions to be exposed by the server. Tools can be static or dynamic. | [] | | port | number | The port on which the server will listen. Only applicable when app is not provided. | 3000 | | logLevel | "debug" \| "info" \| "warn" \| "error" | The minimum logging level for the server. | "info" | | strict | boolean | Enables or disables strict input validation against the tool's inputSchema. When true, requests with invalid input will receive validation errors. | true |

Tool Definition Properties

When defining a tool, whether static or dynamic, the following properties are available:

| Property | Type | Description | | :--------------- | :-------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | string | A unique identifier for the tool. | | description | string | A brief explanation of what the tool does. | | inputSchema | object (JSON Schema) | A JSON Schema defining the expected input arguments for the tool's implementation. | | outputSchema | object (JSON Schema) | An optional JSON Schema defining the expected output structure of the tool's implementation. This is primarily for documentation and validation purposes. | | implementation | (args: object, context: { traceId?: string, traceparent?: string, req: Request }) => Promise<any> | The asynchronous function that contains the core logic of the tool. It receives validated args and a context object containing tracing information and the original Express request object. | | middlewares | Array<Middleware> | An optional array of Express-style middleware functions that will be executed in order before the implementation of the tool. Each middleware receives (req, res, next) and can modify the request, short-circuit the response, or pass control to the next middleware or the implementation. See Middleware Usage for more details. | | resolver | ({ req: Request }) => Promise<Omit<Tool, 'name' \| 'resolver'>> | (For Dynamic Tools Only) An asynchronous function that resolves the description, inputSchema, outputSchema, implementation, and middlewares of the tool dynamically based on the incoming Express Request object. The name of a dynamic tool must be static. |

Usage

Basic Example

import { MCPServer } from "@bonniernews/mcp-server";
import { fatalErrorCounter } from "../metrics.js";

// When no 'app' is provided, MCPServer creates its own Express app and
// handles body parsing, correlation IDs, and a healthcheck route.
const mcpServer = new MCPServer({
  tools: [
    {
      name: "echo",
      description: "Echoes the input back",
      inputSchema: {
        type: "object",
        properties: {
          message: {
            type: "string",
            description: "The message to echo",
          },
        },
        required: ["message"],
      },
      onError: ({ error, req }) => {
        fatalErrorCounter.inc();
      },
      implementation: async (
        args: { message: string },
        context: { traceId?: string; traceparent?: string; req: Request }
      ) => {
        return { echoedMessage: args.message };
      },
    },
  ],
  port: 3000,
  logLevel: "debug",
});

mcpServer
  .start()
  .then(() => {
    console.log("MCP Server started on port 3000");
  })
  .catch((error) => {
    console.error("Failed to start MCP Server:", error);
  });

Example with Custom Express App and Path

You can provide your own Express application instance and specify a custom path for the MCP routes. When app is provided, MCPServer will not add its own body parsing, correlation ID middleware, or healthcheck route, giving you full control over your Express app's configuration. In this scenario, you are responsible for starting your Express app.

import { MCPServer } from "@bonniernews/mcp-server";
import express from "express";
import { middleware as correlatorMiddleware } from "@bonniernews/logger";

const app = express();
app.use(express.json()); // Ensure JSON body parsing is handled by your app
app.use(correlatorMiddleware()); // Add other middleware as needed

app.get("/health", (req, res) => {
  res.status(200).send("Application is healthy!");
});

const mcpServer = new MCPServer({
  app: app, // Provide your existing Express app
  path: "/api/mcp", // Custom path for MCP routes
  tools: [
    {
      name: "greet",
      description: "Greets the user",
      inputSchema: {
        type: "object",
        properties: {
          name: {
            type: "string",
            description: "The name to greet",
          },
        },
        required: ["name"],
      },
      implementation: async (args: { name: string }) => {
        return { greeting: `Hello, ${args.name}!` };
      },
    },
  ],
  logLevel: "info",
});

const port = 3000;
app
  .listen(port, () => {
    console.log(`Express app with MCP routes mounted on /api/mcp listening on port ${port}`);
  })
  .on("error", (err) => {
    console.error("Failed to start Express app:", err);
  });

Example with Atom Schema

This example demonstrates how to use the _options custom parameter, which will be transformed into an enum when the x-atom-schema: true header is sent, otherwise all custom properties will be filtered.

import { MCPServer, fr } from "@bonniernews/mcp-server";

const mcpServer = new MCPServer({
  tools: [
    {
      name: "orderCoffee",
      description: "Orders a specific type of coffee",
      inputSchema: {
        type: "object",
        properties: {
          coffeeType: {
            type: "string",
            description: "The type of coffee to order",
            _options: [
              { value: "latte", label: "Latte" },
              { value: "cappuccino", label: "Cappuccino" },
              { value: "espresso", label: "Espresso" },
            ],
          },
          size: {
            type: "string",
            description: "The size of the coffee",
            enum: ["small", "medium", "large"],
          },
        },
        required: ["coffeeType", "size"],
      },
      implementation: async (args: { coffeeType: string; size: string }) => {
        return `Ordering a ${args.size} ${args.coffeeType}.`;
      },
    },
  ],
  port: 3000,
  logLevel: "debug",
});

mcpServer
  .start()
  .then(() => {
    console.log("MCP Server started on port 3000");
  })
  .catch((error) => {
    console.error("Failed to start MCP Server:", error);
  });

Example with FunctionResponse

This example shows how to wrap the tool's response using FunctionResponse for additional metadata like caching and usage supported in Atom.

import { MCPServer, fr } from "@bonniernews/mcp-server";

const mcpServer = new MCPServer({
  tools: [
    {
      name: "fetchUserData",
      description: "Fetches user data, indicating if the result is cachable",
      inputSchema: {
        type: "object",
        properties: {
          userId: {
            type: "string",
            description: "The ID of the user to fetch",
          },
        },
        required: ["userId"],
      },
      implementation: async (args: { userId: string }) => {
        try {
          // do some api call
          const response = await fetch("väder.com");
          return fr.wrap({
            response: await response.json(),
            cachable: true,
            usage: {
              total_tokens: 10,
              prompt_tokens: 5,
              completion_tokens: 5,
            },
            meta: {
              source: "database",
              queryTime: Date.now(),
            },
          });
        } catch (error) {
          // never throw error in function, and dont cache error responses
          return fr.uncachable({
            status: error.message,
          });
        }
      },
    },
  ],
  port: 3000,
  logLevel: "debug",
});

mcpServer
  .start()
  .then(() => {
    console.log("MCP Server started on port 3000");
  })
  .catch((error) => {
    console.error("Failed to start MCP Server:", error);
  });

Example with Dynamic Tools

This example demonstrates how to define individual tools whose description, inputSchema, outputSchema, and implementation are resolved dynamically based on the incoming request. The name of the dynamic tool must be static. This allows for context-aware tool behavior without needing to fetch all tool definitions for every single tool call.

import { MCPServer } from "@bonniernews/mcp-server";
import type { Request } from "express";

const mcpServer = new MCPServer({
  tools: [
    {
      // Define a dynamic tool with a static name
      name: "userActions",
      resolver: async ({ req: Request }) => {
        const userRole = req.headers["x-user-role"] || "guest"; // Example: get role from header

        if (userRole === "admin") {
          return {
            description: "Performs administrative actions",
            inputSchema: {
              type: "object",
              properties: {
                action: {
                  type: "string",
                  enum: ["audit", "manageUsers"],
                  description: "Admin action to perform",
                },
              },
              required: ["action"],
            },
            implementation: async (args: { action: string }) => {
              return { status: `Admin action '${args.action}' executed.` };
            },
          };
        } else {
          return {
            description: "Performs regular user actions",
            inputSchema: {
              type: "object",
              properties: {
                query: {
                  type: "string",
                  description: "A general query for user functions",
                },
              },
              required: ["query"],
            },
            implementation: async (args: { query: string }) => {
              return { result: `User action for query '${args.query}' processed.` };
            },
          };
        }
      },
    },
  ],
  port: 3000,
  logLevel: "debug",
});

mcpServer
  .start()
  .then(() => {
    console.log("MCP Server started on port 3000");
  })
  .catch((error) => {
    console.error("Failed to start MCP Server:", error);
  });

Example with addTool function

You can add tools to the MCPServer instance after its initialization using the addTool method. This allows for dynamic registration of tools at runtime.

import { initMcpServer } from "@bonniernews/mcp-server";

const mcpServer = new initMcpServer({
  port: 3000,
  logLevel: "debug",
});

// Add a static tool after server initialization
mcpServer.addTool({
  name: "weather",
  description: "Gets the current weather for a location",
  inputSchema: {
    type: "object",
    properties: {
      location: {
        type: "string",
        description: "The city or region to get weather for",
      },
    },
    required: ["location"],
  },
  implementation: async (args: { location: string }) => {
    // Simulate fetching weather data
    return { temperature: "25°C", conditions: "Sunny", location: args.location };
  },
});

// Add a dynamic tool after server initialization
mcpServer.addTool({
  name: "personalAssistant",
  resolver: async ({ req }) => {
    const userId = req.headers["x-user-id"]; // Example: get user ID from header
    if (userId === "admin123") {
      return {
        description: "Admin personal assistant actions",
        inputSchema: {
          type: "object",
          properties: {
            task: {
              type: "string",
              enum: ["scheduleMeeting", "reviewReports"],
              description: "Admin task to perform",
            },
          },
          required: ["task"],
        },
        implementation: async (args: { task: string }) => {
          return { status: `Admin assistant executing: ${args.task}` };
        },
      };
    } else {
      return {
        description: "General user personal assistant actions",
        inputSchema: {
          type: "object",
          properties: {
            query: {
              type: "string",
              description: "A general personal assistant query",
            },
          },
          required: ["query"],
        },
        implementation: async (args: { query: string }) => {
          return { result: `Assistant processing user query: ${args.query}` };
        },
      };
    }
  },
});

mcpServer
  .start()
  .then(() => {
    console.log("MCP Server started on port 3000 with dynamically added tools");
  })
  .catch((error) => {
    console.error("Failed to start MCP Server:", error);
  });

Middleware Usage

Middlewares provide a powerful way to intercept and process requests before they reach the tool's implementation. They are similar to Express middlewares and can modify req.body.params.arguments, short-circuit the response, or handle errors.

Middleware that modifies arguments

This example shows a middleware modifying the incoming arguments before they reach the tool's implementation.

import { MCPServer } from "@bonniernews/mcp-server";

const mcpServer = new MCPServer({
  tools: [
    {
      name: "middleware-tool",
      description: "A tool with middleware that modifies arguments",
      middlewares: [
        (req, _, next) => {
          // Add a new argument
          req.body.params.arguments.modifiedByMiddleware = true;
          next(); // Pass control to the next middleware or the implementation
        },
      ],
      implementation: async (args: { modifiedByMiddleware: boolean }) => {},
    },
  ],
  port: 3000,
});

mcpServer.start();