Skip to main content

Model Context Protocol (MCP) Libraries for Node.js 2026

·PkgPulse Team
0

TL;DR

Model Context Protocol (MCP) is Anthropic's open standard for connecting AI models to data sources — and it's growing fast. In 2026, MCP lets you expose tools, resources, and prompts to any MCP-compatible AI client (Claude Desktop, Cursor, your own apps). The official @modelcontextprotocol/sdk is the foundation; FastMCP (fastmcp) adds a developer-friendly layer on top. Building an MCP server is genuinely useful when you want your tools usable across multiple AI contexts — not just in your app, but in Claude Desktop, Cursor, and future clients.

Key Takeaways

  • @modelcontextprotocol/sdk: official Anthropic SDK, stdio + SSE transports, ~200K weekly downloads and growing fast
  • fastmcp: developer-friendly wrapper — less boilerplate, Zod schemas, built-in authentication
  • MCP vs tool calling: tool calling is per-request; MCP servers are persistent, reusable across clients
  • Three primitives: Tools (actions), Resources (readable data), Prompts (reusable templates)
  • Transport: stdio (local processes), SSE/WebSocket (remote servers), HTTP (stateless)
  • Use cases: database query tools, file system access, API wrappers, knowledge base search

PackageWeekly DownloadsTrend
@modelcontextprotocol/sdk~210K↑ Growing fast
fastmcp~45K↑ Growing
@anthropic-ai/sdk~800K↑ Growing

MCP is early-stage but growing rapidly as Claude Desktop and Cursor adoption increases.


What is MCP?

MCP (Model Context Protocol) is a standard that lets AI models connect to external data sources and tools. Instead of hardcoding API calls inside your app, you build an MCP server that exposes:

  • Tools: callable functions (like API calls, database queries)
  • Resources: readable data (files, documents, database records)
  • Prompts: reusable prompt templates with parameters

Any MCP-compatible client can then use your server. Build once, use everywhere.


Official SDK: @modelcontextprotocol/sdk

Building a Basic MCP Server

// npm install @modelcontextprotocol/sdk zod
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';

const server = new Server(
  {
    name: 'pkgpulse-mcp',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
      resources: {},
    },
  }
);

// Register available tools:
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'get_package_stats',
      description: 'Get npm package download stats, health score, and metadata',
      inputSchema: {
        type: 'object',
        properties: {
          packageName: { type: 'string', description: 'npm package name (e.g., "react")' },
        },
        required: ['packageName'],
      },
    },
    {
      name: 'compare_packages',
      description: 'Compare two npm packages by downloads, bundle size, and health score',
      inputSchema: {
        type: 'object',
        properties: {
          packageA: { type: 'string' },
          packageB: { type: 'string' },
        },
        required: ['packageA', 'packageB'],
      },
    },
  ],
}));

// Handle tool calls:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  switch (name) {
    case 'get_package_stats': {
      const { packageName } = args as { packageName: string };

      // Fetch from npm API:
      const [downloads, metadata] = await Promise.all([
        fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`).then(r => r.json()),
        fetch(`https://registry.npmjs.org/${packageName}`).then(r => r.json()),
      ]);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              name: packageName,
              weeklyDownloads: downloads.downloads,
              latestVersion: metadata['dist-tags']?.latest,
              description: metadata.description,
              license: metadata.license,
              lastPublished: metadata.time?.[metadata['dist-tags']?.latest],
            }, null, 2),
          },
        ],
      };
    }

    case 'compare_packages': {
      const { packageA, packageB } = args as { packageA: string; packageB: string };

      const [statsA, statsB] = await Promise.all([
        fetchPackageStats(packageA),
        fetchPackageStats(packageB),
      ]);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({ packageA: statsA, packageB: statsB }, null, 2),
          },
        ],
      };
    }

    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// Start the server:
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP server running on stdio');

Registering Resources

import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

// Expose database records as readable resources:
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: 'pkgpulse://packages/trending',
      name: 'Trending npm Packages',
      description: 'Top 50 trending npm packages this week',
      mimeType: 'application/json',
    },
    {
      uri: 'pkgpulse://categories',
      name: 'Package Categories',
      description: 'All package categories with counts',
      mimeType: 'application/json',
    },
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;

  if (uri === 'pkgpulse://packages/trending') {
    const trending = await db.package.findMany({
      orderBy: { weeklyDownloadChange: 'desc' },
      take: 50,
    });
    return {
      contents: [
        {
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(trending, null, 2),
        },
      ],
    };
  }

  throw new Error(`Unknown resource: ${uri}`);
});

FastMCP: Developer-Friendly Alternative

FastMCP wraps the official SDK with better ergonomics — Zod schemas, cleaner tool definition, built-in auth:

// npm install fastmcp zod
import { FastMCP } from 'fastmcp';
import { z } from 'zod';

const mcp = new FastMCP({
  name: 'pkgpulse-mcp',
  version: '1.0.0',
});

// Define tools with Zod schemas (no manual JSON Schema):
mcp.addTool({
  name: 'get_package_stats',
  description: 'Get npm package download stats and health score',
  parameters: z.object({
    packageName: z.string().describe('npm package name, e.g. "react"'),
    includeBundleSize: z.boolean().default(false),
  }),
  execute: async ({ packageName, includeBundleSize }) => {
    const stats = await fetchPackageStats(packageName);

    if (includeBundleSize) {
      const bundle = await fetch(`https://bundlephobia.com/api/size?package=${packageName}`)
        .then(r => r.json());
      return { ...stats, gzipSize: bundle.gzip, parseTime: bundle.assets?.[0]?.js?.parse };
    }

    return stats;
  },
});

mcp.addTool({
  name: 'search_packages',
  description: 'Search npm packages by keyword',
  parameters: z.object({
    query: z.string(),
    category: z.enum(['framework', 'testing', 'orm', 'ui', 'all']).default('all'),
    limit: z.number().min(1).max(20).default(10),
  }),
  execute: async ({ query, category, limit }) => {
    return searchPackages(query, { category, limit });
  },
});

// Add resources:
mcp.addResource({
  uri: 'pkgpulse://trending',
  name: 'Trending Packages',
  description: 'This week\'s top trending npm packages',
  mimeType: 'application/json',
  load: async () => {
    const trending = await getTrendingPackages();
    return JSON.stringify(trending, null, 2);
  },
});

// Start (stdio for local tools, HTTP for remote):
mcp.run({ transport: 'stdio' });
// or: mcp.run({ transport: 'sse', port: 3001 });

Connecting MCP to Claude Desktop

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "pkgpulse": {
      "command": "node",
      "args": ["/path/to/pkgpulse-mcp/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://..."
      }
    }
  }
}

Now Claude Desktop can call your tools directly: "Compare React vs Vue download trends" → Claude calls compare_packages → returns live data.


MCP vs Tool Calling: When to Use Each

Use Tool Calling (Vercel AI SDK / OpenAI function calling) when:
  → Building app-specific features
  → Tools are only needed in your app
  → Simple, stateless tool execution
  → Don't need cross-client reuse

Use MCP when:
  → Want tools available in Claude Desktop, Cursor, etc.
  → Building a data connector (database, API) for AI
  → Team uses multiple AI clients and wants shared tools
  → Building an AI-native product that exposes data
  → Creating developer tools with AI integration

Explore MCP libraries and AI SDK download trends on PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.