Skip to main content

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

·PkgPulse Team

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.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.