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

bare-mcp

v1.1.0

Published

Minimal, general-purpose MCP (Model Context Protocol) server library

Downloads

265

Readme

MCP Server Library

A minimal, general-purpose implementation of the Model Context Protocol (MCP).

Works on Node.js and Bare runtime (Pear/Holepunch).

Features

  • Tools — Register functions that AI clients can call
  • Resources — Expose data that clients can read (static or dynamic)
  • Resource Templates — URI patterns with parameters (user://{id})
  • Annotations — Metadata hints for tools and content (MCP 2025-11-25)
  • Notifications — Push updates to connected clients
  • Subscriptions — Clients can subscribe to resource changes
  • Multiple Transports — HTTP, WebSocket, SSE, stdio

Quick Start

import { createMCPServer } from 'bare-mcp'
import { createHttpTransport } from 'bare-mcp/http'

// Create server
const mcp = createMCPServer({
  name: 'my-server',
  version: '1.0.0'
})

// Register a tool
mcp.addTool({
  name: 'greet',
  description: 'Say hello to someone',
  inputSchema: {
    type: 'object',
    properties: {
      name: { type: 'string', description: 'Name to greet' }
    },
    required: ['name']
  },
  execute: async ({ name }) => `Hello, ${name}!`
})

// Start HTTP server (works on both Node.js and Bare)
await createHttpTransport(mcp, { port: 3000 })

Tools

Tools are functions that AI clients can invoke.

mcp.addTool({
  name: 'calculate',
  description: 'Perform arithmetic',
  inputSchema: {
    type: 'object',
    properties: {
      a: { type: 'number' },
      b: { type: 'number' },
      op: { type: 'string', enum: ['add', 'subtract', 'multiply', 'divide'] }
    },
    required: ['a', 'b', 'op']
  },
  execute: async ({ a, b, op }) => {
    const ops = { add: a + b, subtract: a - b, multiply: a * b, divide: a / b }
    return JSON.stringify({ result: ops[op] })
  }
})

// Register multiple tools
mcp.addTools([tool1, tool2, tool3])

Tool Annotations

Tools can include annotations that describe their behavior:

mcp.addTool({
  name: 'search',
  description: 'Search the web',
  inputSchema: {
    type: 'object',
    properties: { query: { type: 'string' } },
    required: ['query']
  },
  execute: async ({ query }) => `Results for: ${query}`,
  annotations: {
    title: 'Web Search',        // Human-readable title
    readOnlyHint: true,         // Doesn't modify environment (default: false)
    openWorldHint: true         // Interacts with external systems (default: true)
  }
})

// Destructive tool example
mcp.addTool({
  name: 'delete_file',
  description: 'Delete a file',
  inputSchema: {
    type: 'object',
    properties: { path: { type: 'string' } },
    required: ['path']
  },
  execute: async ({ path }) => { /* ... */ },
  annotations: {
    title: 'Delete File',
    readOnlyHint: false,        // Modifies environment
    destructiveHint: true,      // May destroy data (default: true)
    idempotentHint: true,       // Repeated calls have same effect (default: false)
    openWorldHint: false        // Only affects local system
  }
})

| Annotation | Default | Description | |------------|---------|-------------| | title | — | Human-readable display name | | readOnlyHint | false | If true, tool doesn't modify its environment | | destructiveHint | true | If true, tool may destroy data (only when readOnlyHint=false) | | idempotentHint | false | If true, repeated calls have no extra effect (only when readOnlyHint=false) | | openWorldHint | true | If true, interacts with external systems |

Tool Results with Annotations

Tools can return rich content with annotations:

mcp.addTool({
  name: 'analyze',
  execute: async () => [{
    type: 'text',
    text: 'Analysis results...',
    annotations: {
      audience: ['user'],           // Who content is for: 'user', 'assistant', or both
      priority: 0.9                  // Importance: 0.0 (optional) to 1.0 (required)
    }
  }]
})

// Return error with content
mcp.addTool({
  name: 'fetch',
  execute: async () => ({
    content: [{ type: 'text', text: 'Connection timeout' }],
    isError: true
  })
})

Error Handling

Tools can throw MCPError with specific error codes:

import { MCPError, ErrorCode } from 'bare-mcp'

mcp.addTool({
  name: 'get_user',
  inputSchema: {
    type: 'object',
    properties: { id: { type: 'string' } },
    required: ['id']
  },
  execute: async ({ id }) => {
    const user = await db.findUser(id)
    if (!user) {
      throw new MCPError(
        ErrorCode.INVALID_PARAMS,
        `User not found: ${id}`,
        { userId: id }  // Optional data field
      )
    }
    return JSON.stringify(user)
  }
})

// Custom error codes (use values > -32000)
mcp.addTool({
  name: 'rate_limited_api',
  execute: async () => {
    throw new MCPError(-32001, 'Rate limit exceeded', { retryAfter: 60 })
  }
})

Standard Error Codes:

| Code | Name | Description | |------|------|-------------| | -32700 | PARSE_ERROR | Invalid JSON | | -32600 | INVALID_REQUEST | Not a valid JSON-RPC request | | -32601 | METHOD_NOT_FOUND | Method does not exist | | -32602 | INVALID_PARAMS | Invalid parameters (validation, missing args) | | -32603 | INTERNAL_ERROR | Internal server error | | -32002 | RESOURCE_NOT_FOUND | Resource not found |

Error responses follow the JSON-RPC 2.0 spec:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "User not found: abc123",
    "data": { "userId": "abc123" }
  }
}

Resources

Resources expose data that clients can read.

Static Resource

mcp.addResource({
  uri: 'config://app',
  name: 'App Configuration',
  description: 'Application settings',
  mimeType: 'application/json',
  text: JSON.stringify({ theme: 'dark', version: '1.0' })
})

Dynamic Resource

mcp.addResource({
  uri: 'stats://live',
  name: 'Live Statistics',
  mimeType: 'application/json',
  read: async () => JSON.stringify({
    uptime: process.uptime(),
    memory: process.memoryUsage()
  })
})

Resource Annotations

Resources support annotations for display hints and content metadata:

mcp.addResource({
  uri: 'doc://readme',
  name: 'README',
  title: 'Project Documentation',     // Human-readable title
  mimeType: 'text/markdown',
  text: '# My Project',
  annotations: {
    audience: ['user'],               // Who content is for
    priority: 0.9,                    // Importance (0.0 to 1.0)
    lastModified: '2025-01-15T10:00:00Z'
  }
})

Dynamic resources can return annotations per-read:

mcp.addResource({
  uri: 'cache://data',
  name: 'Cached Data',
  read: async () => ({
    text: JSON.stringify(getCachedData()),
    annotations: {
      lastModified: new Date().toISOString(),
      audience: ['assistant']
    }
  })
})

| Annotation | Type | Description | |------------|------|-------------| | audience | string[] | Who content is for: ["user"], ["assistant"], or ["user", "assistant"] | | priority | number | Importance: 0.0 (optional) to 1.0 (required) | | lastModified | string | ISO 8601 timestamp of last modification |

Resource Templates

URI patterns that extract parameters:

mcp.addResourceTemplate({
  uriTemplate: 'user://{id}',
  name: 'User by ID',
  description: 'Fetch user details',
  mimeType: 'application/json',
  read: async ({ id }) => {
    const user = await db.getUser(id)
    return JSON.stringify(user)
  }
})

// Client can read: user://alice, user://bob, etc.

Notifications

Push updates to connected clients.

// Resource was modified
mcp.notifyResourceUpdated('stats://live')

// Resource list changed (added/removed)
mcp.notifyResourceListChanged()

// Tool list changed
mcp.notifyToolListChanged()

// Progress update for long operations
mcp.notifyProgress('upload-token', 50, 100)

// Custom notification
mcp.notify('notifications/custom', { data: 'anything' })

Transports

Runtime detection is automatic — bare-mcp/http and bare-mcp/stdio use which-runtime to pick the correct implementation (Node.js or Bare) at import time. Downstream packages never need to worry about it.

HTTP Transport

The HTTP transport supports three connection modes, all served from the same server:

| Mode | Endpoint | Protocol | Direction | |------|----------|----------|-----------| | Streamable HTTP | POST /mcp | JSON-RPC over HTTP | Request → Response | | SSE | GET /sse + POST /message | JSON-RPC over SSE | Bidirectional | | WebSocket | ws://host:port | JSON-RPC over WS | Bidirectional |

Starting the Server

import { createMCPServer } from 'bare-mcp'
import { createHttpTransport } from 'bare-mcp/http'

const mcp = createMCPServer({ name: 'my-server', version: '1.0.0' })

mcp.addTool({
  name: 'greet',
  description: 'Say hello',
  inputSchema: {
    type: 'object',
    properties: { name: { type: 'string' } },
    required: ['name']
  },
  execute: async ({ name }) => `Hello, ${name}!`
})

const transport = await createHttpTransport(mcp, {
  port: 3000,
  host: '0.0.0.0',
  websocket: true,   // Enable WebSocket (default: true, Node.js only)
  verbose: false,     // Log requests/notifications to stderr (default: false)
  onActivity: (entry) => console.log('Tool called:', entry.tool)
})

HTTP Endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /mcp or / | JSON-RPC endpoint (Streamable HTTP) | | GET | /sse | SSE stream (bidirectional MCP transport) | | POST | /message?sessionId=... | SSE message endpoint (paired with /sse) | | GET | /health | Health check ({ status, server, version, requestCount }) | | GET | /activity | Recent tool call activity log | | POST | /activity/clear | Clear activity log | | WS | ws://host:port | WebSocket (Node.js only) |

Streamable HTTP (Recommended)

The simplest mode. Clients send a JSON-RPC request via POST and receive the response in the HTTP body. This is the transport that Cursor, Claude Code, and most modern MCP clients use.

// Client sends a request
const res = await fetch('http://localhost:3000/mcp', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'initialize',
    params: {
      protocolVersion: '2025-03-26',
      capabilities: {},
      clientInfo: { name: 'my-client', version: '1.0.0' }
    },
    id: 1
  })
})
const { result } = await res.json()
// result.serverInfo, result.capabilities, etc.

// Call a tool
const toolRes = await fetch('http://localhost:3000/mcp', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'tools/call',
    params: { name: 'greet', arguments: { name: 'World' } },
    id: 2
  })
})
const { result: toolResult } = await toolRes.json()
// toolResult.content[0].text === 'Hello, World!'

Notifications (no id field) receive a 204 No Content response:

await fetch('http://localhost:3000/mcp', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'notifications/initialized'
  })
})
// 204 No Content

SSE Transport (Legacy)

The MCP SSE transport is bidirectional. The client opens an SSE stream, receives a POST endpoint URL, then sends JSON-RPC requests to that URL. Responses and server notifications arrive on the SSE stream.

// 1. Open SSE connection
const events = new EventSource('http://localhost:3000/sse')

let messageEndpoint = null

// 2. Wait for the endpoint event (sent immediately on connect)
events.addEventListener('endpoint', (e) => {
  messageEndpoint = e.data
  // e.g. "http://localhost:3000/message?sessionId=client-1-1234567890"
})

// 3. Listen for responses and notifications on the SSE stream
events.addEventListener('message', (e) => {
  const msg = JSON.parse(e.data)

  if (msg.id) {
    // Response to a request you sent
    console.log('Response:', msg.result)
  } else if (msg.method) {
    // Server-initiated notification
    console.log('Notification:', msg.method, msg.params)
  }
})

// 4. Send JSON-RPC requests by POSTing to the endpoint
await fetch(messageEndpoint, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'tools/call',
    params: { name: 'greet', arguments: { name: 'World' } },
    id: 1
  })
})
// HTTP response is 202 Accepted — the actual result arrives on the SSE stream

WebSocket (Node.js only)

Full bidirectional JSON-RPC over WebSocket. Supports subscriptions and real-time notifications.

const ws = new WebSocket('ws://localhost:3000')

ws.onopen = () => {
  // Initialize
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'initialize',
    params: {
      protocolVersion: '2025-03-26',
      capabilities: {},
      clientInfo: { name: 'ws-client', version: '1.0.0' }
    },
    id: 1
  }))

  // Call a tool
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'tools/call',
    params: { name: 'greet', arguments: { name: 'World' } },
    id: 2
  }))

  // Subscribe to resource updates
  ws.send(JSON.stringify({
    jsonrpc: '2.0',
    method: 'resources/subscribe',
    params: { uri: 'stats://live' },
    id: 3
  }))
}

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data)

  if (msg.type === 'connected') {
    // Initial connection status
    console.log('Connected as:', msg.clientId)
  } else if (msg.id) {
    // Response to a request
    console.log('Response:', msg.result)
  } else if (msg.method) {
    // Server notification (resource updates, progress, etc.)
    console.log('Notification:', msg.method, msg.params)
  }
}

Transport Return Object

createHttpTransport() returns:

| Property | Type | Description | |----------|------|-------------| | port | number | Bound port | | host | string | Bound host | | httpServer | http.Server | Underlying HTTP server | | wss | WebSocketServer \| null | WebSocket server (Node.js, if enabled) | | wsClients | Map | Connected WebSocket clients | | sseClients | Map | Connected SSE clients | | activityLog | Array | Recent tool call activity | | requestCount() | function | Returns total request count | | broadcast(msg) | function | Send to all clients (WS + SSE) | | close() | async function | Graceful shutdown |

stdio Transport

For Claude Desktop and similar clients that communicate over stdin/stdout:

import { createMCPServer } from 'bare-mcp'
import { createStdioTransport } from 'bare-mcp/stdio'

const mcp = createMCPServer({ name: 'my-server', version: '1.0.0' })
mcp.addTool({ /* ... */ })

await createStdioTransport(mcp, {
  onActivity: (entry) => console.error('Tool:', entry.tool),
  onClose: () => process.exit(0)
})

Configuring MCP Clients

Claude Desktop (stdio)

~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/path/to/server.js"]
    }
  }
}

Claude Desktop (HTTP)

{
  "mcpServers": {
    "my-server": {
      "url": "http://localhost:3000/mcp"
    }
  }
}

Cursor (HTTP)

In Cursor Settings → MCP, add the server URL:

http://localhost:3000/mcp

Cursor uses the Streamable HTTP transport (POST /mcp).

Generic MCP Client (HTTP)

Any MCP client that supports Streamable HTTP can connect by pointing at the /mcp endpoint:

http://your-host:3000/mcp

Clients that use the legacy SSE transport should connect to:

http://your-host:3000/sse

MCP Methods

| Method | Description | |--------|-------------| | initialize | Initialize connection, get capabilities | | tools/list | List available tools | | tools/call | Execute a tool | | resources/list | List available resources | | resources/templates/list | List resource templates | | resources/read | Read a resource by URI | | resources/subscribe | Subscribe to resource updates | | resources/unsubscribe | Unsubscribe from updates | | ping | Health check |

Notification Types

| Method | Description | |--------|-------------| | notifications/resources/updated | A resource's content changed | | notifications/resources/list_changed | Resources added/removed | | notifications/tools/list_changed | Tools added/removed | | notifications/progress | Progress update |

API Reference

createMCPServer(options)

Create an MCP server instance.

const mcp = createMCPServer({
  name: 'my-server',           // Server name
  version: '1.0.0',            // Server version
  protocolVersion: '2025-11-25' // MCP protocol version
})

Returns an object with:

  • addTool(tool) / addTools(tools[]) — Register tools
  • addResource(resource) / addResources(resources[]) — Register resources
  • addResourceTemplate(template) — Register URI template
  • readResource(uri) — Read a resource
  • notify(method, params) — Send notification
  • notifyResourceUpdated(uri) — Notify resource changed
  • notifyResourceListChanged() — Notify resources added/removed
  • notifyToolListChanged() — Notify tools added/removed
  • notifyProgress(token, progress, total?) — Send progress
  • handleRequest(method, params) — Handle JSON-RPC request

createHttpTransport(mcp, options)

Start HTTP server with WebSocket and SSE support.

const transport = await createHttpTransport(mcp, {
  port: 3000,
  host: '0.0.0.0',
  websocket: true,
  onActivity: (entry) => {}
})

Returns:

  • port, host — Bound address
  • httpServer — Node.js HTTP server
  • wss — WebSocket server
  • broadcast(message) — Send to all clients
  • close() — Shutdown server

createStdioTransport(mcp, options)

Start stdio transport for CLI usage.

const transport = await createStdioTransport(mcp, {
  onActivity: (entry) => {},
  onClose: () => {}
})

Bare Runtime (Pear)

This library uses which-runtime to automatically detect whether you're on Node.js or Bare and load the correct transport implementation. Your code is the same either way:

import { createMCPServer } from 'bare-mcp'
import { createHttpTransport } from 'bare-mcp/http'  // Auto-detects runtime
import { createStdioTransport } from 'bare-mcp/stdio'  // Auto-detects runtime

const mcp = createMCPServer({ name: 'my-app' })
mcp.addTool({ /* ... */ })

await createHttpTransport(mcp, { port: 3000 })

Explicit Imports

If you need to bypass runtime detection and target a specific implementation:

| Transport | Node.js | Bare | |-----------|---------|------| | HTTP | bare-mcp/http-node | bare-mcp/http-bare | | stdio | bare-mcp/stdio-node | bare-mcp/stdio-bare |

Transport Differences

| | Node.js | Bare | |---|---------|------| | HTTP | node:http + ws — Streamable HTTP, SSE, WebSocket | bare-http1 — Streamable HTTP, SSE (no WebSocket) | | stdio | node:readline | Raw process.stdin/stdout |

Dependencies

For Node.js:

npm install bare-mcp ws

For Bare/Pear:

npm install bare-mcp bare-http1

License

MIT