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

mcp-sdk

v1.1.0

Published

MCP developer toolkit | consume, build, proxy, and aggregate SSE MCP servers

Readme

mcp-sdk

Consume MCP servers in your app — or from the terminal.

npm install mcp-sdk
import { MCP } from "mcp-sdk";

const mcp = await MCP.connect("https://example.com/mcp");
const text = await mcp.callText("search_web", { query: "hello" });
await mcp.disconnect();

What is this?

Model Context Protocol (MCP) lets AI models call tools hosted on remote servers. Most MCP tooling is aimed at building servers. This package is aimed at using them.

mcp-sdk is two things in one:

| | What it is | Who it's for | |---|---|---| | SDK | A TypeScript library for connecting to MCP servers in your app | App developers | | CLI | A terminal tool that proxies remote servers to AI clients | AI power users |

If you're building an AI app that needs tools, you want the SDK. If you're wiring up Claude Desktop or Cursor to a remote MCP server, you want the CLI.


SDK

Installation

npm install mcp-sdk

Quick start

import { MCP } from "mcp-sdk";

const mcp = await MCP.connect("https://example.com/mcp");

// List available tools
const tools = await mcp.tools();
console.log(tools.map(t => t.name));

// Call a tool — returns raw MCP result
const result = await mcp.call("search_web", { query: "hello world" });
console.log(result.content); // [{ type: "text", text: "..." }]

// Convenience: get the first text block as a plain string
const text = await mcp.callText("search_web", { query: "hello world" });
console.log(text); // "The search returned..."

// Get ALL text blocks joined by newline (for multi-block responses)
const full = await mcp.callAllText("list_items", {});

await mcp.disconnect();

Transport

By default, mcp-sdk auto-detects the transport protocol — it tries Streamable HTTP (MCP spec 2025-03-26+) first and falls back to SSE automatically. You can also be explicit:

// Auto-detect (default)
const mcp = await MCP.connect("https://example.com/mcp");

// Force Streamable HTTP (newer servers)
const mcp = await MCP.connect("https://example.com/mcp", { transport: "http" });

// Force SSE (legacy servers)
const mcp = await MCP.connect("https://example.com/sse", { transport: "sse" });

Authentication

Pass headers as part of options:

const mcp = await MCP.connect("https://api.example.com/mcp", {
  headers: {
    Authorization: `Bearer ${process.env.MCP_TOKEN}`,
  },
});

Middleware

Add middleware to intercept every tool call. Middleware is composable and runs in the order added.

import { MCP, logger, cache, retry } from "mcp-sdk";

const mcp = await MCP.connect("https://example.com/sse", {
  middleware: [
    retry(3),       // retry failed calls up to 3 times (exponential backoff)
    cache(),        // cache successful results for 60 seconds
    logger(),       // log every call with timing and outcome
  ],
});

Or add middleware after connecting — returns this so it's chainable:

const mcp = (await MCP.connect(url)).use(logger()).use(cache());

Built-in middleware:

| Middleware | What it does | |---|---| | logger() | Logs tool name, args, response time, outcome to stderr | | cache(ttlMs?) | Caches successful results in memory. Default TTL: 60s | | retry(n, backoffMs?) | Retries failures with exponential backoff |

You can write your own:

import type { MiddlewareFn } from "mcp-sdk";

const myMiddleware: MiddlewareFn = async (ctx, next) => {
  console.log("calling", ctx.toolName, "on", ctx.serverUrl);
  const result = await next();
  console.log("done");
  return result;
};

Multiple servers

Connect to multiple servers at once. Tools are automatically namespaced by hostname so names never collide.

import { MCP } from "mcp-sdk";

const mcp = await MCP.aggregate([
  "https://github-mcp.com/sse",
  "https://linear-mcp.com/sse",
  "https://notion-mcp.com/sse",
]);

// Tools are prefixed: "github__search_repos", "linear__create_issue", etc.
const repos  = await mcp.callText("github__search_repos", { q: "mcp" });
const issue  = await mcp.call("linear__create_issue", { title: "Fix bug" });
const page   = await mcp.call("notion__create_page", { title: "Notes" });

await mcp.disconnect();

Per-server auth with shared middleware:

const mcp = await MCP.aggregate(
  [
    { url: "https://github-mcp.com/sse",  headers: { Authorization: `Bearer ${GH_TOKEN}`  } },
    { url: "https://linear-mcp.com/sse",  headers: { Authorization: `Bearer ${LIN_TOKEN}` } },
  ],
  {
    middleware: [logger(), retry(3)],
  }
);

Framework integrations

React

npm install mcp-sdk react

Single serveruseMCP:

import { useMCP } from "mcp-sdk/react";

function SearchWidget() {
  const { call, callText, tools, loading, error } = useMCP(
    "https://example.com/mcp",
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );

  if (loading) return <p>Connecting...</p>;
  if (error)   return <p>Error: {error.message}</p>;

  return (
    <div>
      <p>{tools.length} tools available</p>
      <button onClick={() => callText("search_web", { query: "hello" })}>
        Search
      </button>
    </div>
  );
}

Multiple serversuseAggregateMCP:

import { useAggregateMCP } from "mcp-sdk/react";

function MultiWidget() {
  const { call, tools, loading } = useAggregateMCP([
    { url: "https://github-mcp.com/mcp", headers: { Authorization: `Bearer ${GH_TOKEN}` } },
    { url: "https://linear-mcp.com/mcp", headers: { Authorization: `Bearer ${LIN_TOKEN}` } },
  ]);

  if (loading) return <p>Connecting...</p>;

  // Tools are namespaced: "github__search_repos", "linear__create_issue"
  return <p>{tools.map(t => t.name).join(", ")}</p>;
}

Both hooks reconnect automatically when URLs change and disconnect on unmount. Wrap options in useMemo if it contains dynamic values to avoid unnecessary reconnects.

Next.js (App Router)

Create a route handler that proxies tool calls to a remote MCP server. A new connection is made per request — works correctly in serverless environments.

npm install mcp-sdk
// app/api/mcp/route.ts
import { createMCPHandler } from "mcp-sdk/next";

export const POST = createMCPHandler("https://example.com/sse", {
  headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
});

Call it from your frontend:

const res = await fetch("/api/mcp", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ tool: "search_web", args: { query: "hello" } }),
});
const result = await res.json();

Express

npm install mcp-sdk express

Stateless (one connection per request — simple, works anywhere):

import { mcpHandler } from "mcp-sdk/express";

app.use(express.json());
app.post("/mcp", mcpHandler("https://example.com/sse"));

Persistent (one connection reused across requests — better for production):

import { MCP, logger, cache } from "mcp-sdk";

const mcp = await MCP.connect("https://example.com/sse", {
  middleware: [logger(), cache()],
});

app.post("/mcp/:tool", async (req, res, next) => {
  try {
    const result = await mcp.call(req.params.tool, req.body);
    res.json(result);
  } catch (err) {
    next(err);
  }
});

Full API reference

MCP.connect(url, options?)

Connect to a single SSE MCP server. Returns a connected MCPClient.

const mcp = await MCP.connect(url, {
  headers?: Record<string, string>,
  middleware?: MiddlewareFn[],
});

MCP.aggregate(servers, options?)

Connect to multiple servers. Each entry can be a URL string or a ServerConfig object for per-server options. Returns a connected MCPClient.

const mcp = await MCP.aggregate(
  [
    "https://server-a.com/sse",
    { url: "https://server-b.com/sse", headers: { ... } },
  ],
  {
    headers?: Record<string, string>,   // applied to all servers
    middleware?: MiddlewareFn[],
  }
);

MCPClient

| Method | Description | |---|---| | .call(tool, args?) | Call a tool. Returns CallToolResult (raw MCP type). | | .callText(tool, args?) | Call a tool, return the first text block as a string. | | .callAllText(tool, args?) | Call a tool, return all text blocks joined by \n. | | .tools() | List all available tools. | | .resources() | List all available resources. | | .prompts() | List all available prompts. | | .use(middleware) | Add middleware. Returns this. | | .disconnect() | Close all connections. |

Errors

import { MCPConnectionError, MCPToolError } from "mcp-sdk";

try {
  const mcp = await MCP.connect("https://example.com/mcp");
} catch (err) {
  if (err instanceof MCPConnectionError) {
    console.error("Could not reach server:", err.serverUrl);
  }
}

try {
  await mcp.callText("search_web", { query: "hello" });
} catch (err) {
  if (err instanceof MCPToolError) {
    console.error("Tool failed:", err.toolName);
  }
}

CLI

The CLI is built on the same SDK. Use it to wire remote MCP servers into AI clients or debug them from the terminal.

Install

npm install -g mcp-sdk
# or just use npx — no install needed
npx mcp-sdk --help

serve (default)

Proxy one or more SSE MCP servers as a local stdio MCP server. This is what you put in your AI client config.

mcp-sdk <url...> [options]
mcp-sdk serve <url...> [options]   # explicit

| Option | Description | |---|---| | --no-log | Disable call logging to stderr | | --cache | Cache identical tool call results | | --cache-ttl <ms> | Cache TTL (default: 60000) | | --retry <n> | Retry failures up to N times | | -H <key=value> | Add an HTTP header (repeatable) |

# Single server
mcp-sdk https://example.com/sse

# Multiple servers — auto-namespaced
mcp-sdk https://github-mcp.com/sse https://linear-mcp.com/sse

# With auth and middleware
mcp-sdk https://api.example.com/sse \
  -H "Authorization=Bearer $TOKEN" \
  --cache --retry 3

inspect

Print everything a server exposes — tools, resources, prompts — without starting a proxy.

mcp-sdk inspect <url> [-H <key=value>]
Server: https://example.com/sse
────────────────────────────────────────────────────────────

TOOLS (3)
  search_web(query: string, limit?: number)
    Search the web and return results
  get_weather(location: string)
    Get current weather for a location
  summarize(url: string)
    Fetch and summarize a URL

RESOURCES (1)
  file:///{path} — Local file system

PROMPTS (0)
  none

call

Call a tool directly and print the result. Useful for testing.

mcp-sdk call <url> <tool> [json-args] [-H <key=value>]
mcp-sdk call https://example.com/sse search_web '{"query":"hello"}'
mcp-sdk call https://api.example.com/sse my_tool '{}' -H "Authorization=Bearer $TOKEN"

Wiring into AI clients

Claude Desktop — edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["mcp-sdk", "https://example.com/sse", "-H", "Authorization=Bearer YOUR_TOKEN"]
    }
  }
}

Multiple servers in one entry:

{
  "mcpServers": {
    "everything": {
      "command": "npx",
      "args": [
        "mcp-sdk",
        "https://github-mcp.com/sse",
        "https://linear-mcp.com/sse",
        "https://slack-mcp.com/sse"
      ]
    }
  }
}

Cursor — edit .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["mcp-sdk", "https://example.com/sse"]
    }
  }
}

Server

Build your own MCP server — without the boilerplate of the official SDK.

Define tools individually with defineTool, then pass them to createServer. Each tool is a self-contained unit: a name, a description, a Zod input schema, and a run function.

npm install mcp-sdk zod

Quick start

import { defineTool, createServer } from "mcp-sdk/server";
import { z } from "zod";

const getUser = defineTool({
  name: "get_user",
  description: "Fetch a user by ID",
  input: z.object({
    id: z.string().describe("The user's ID"),
  }),
  run: async ({ id }, { headers }) => {
    const res = await fetch(`https://api.example.com/users/${id}`, { headers });
    return res.json();
  },
});

const server = createServer({
  tools: [getUser],
  auth: { type: "bearer", token: process.env.API_TOKEN! },
});

await server.listen(); // stdio — ready for Claude Desktop, Cursor, etc.

Define tools in separate files

Tools are plain objects — split them across files however makes sense for your project.

// tools/search-users.ts
import { defineTool } from "mcp-sdk/server";
import { z } from "zod";

export const searchUsers = defineTool({
  name: "search_users",
  description: "Search users by name or email",
  input: z.object({
    query: z.string().describe("Name or email fragment"),
    limit: z.number().default(10).describe("Max results"),
  }),
  run: async ({ query, limit }, { headers }) => {
    const res = await fetch(
      `https://api.example.com/users?q=${query}&limit=${limit}`,
      { headers }
    );
    return res.json();
  },
});
// index.ts
import { createServer } from "mcp-sdk/server";
import { searchUsers } from "./tools/search-users.js";
import { createIssue } from "./tools/create-issue.js";

const server = createServer({
  tools: [searchUsers, createIssue],
  auth: { type: "bearer", token: process.env.API_TOKEN! },
});

await server.listen();

Auth

Auth is configured once on the server and injected into every tool as ctx.headers — ready to drop into fetch. Tools never handle credentials directly.

// Bearer token
createServer({ auth: { type: "bearer", token: process.env.TOKEN! }, tools });

// API key header
createServer({ auth: { type: "apikey", header: "X-API-Key", value: process.env.KEY! }, tools });

// HTTP Basic
createServer({ auth: { type: "basic", username: "user", password: process.env.PASS! }, tools });

// No auth (default)
createServer({ tools });

Inside a tool, use it directly:

run: async ({ id }, { headers }) => {
  return fetch(`https://api.example.com/users/${id}`, { headers }).then(r => r.json());
}

Return anything

run can return a string, number, object, or array — the SDK serializes it to MCP content automatically. Throw to signal a tool error.

run: async () => "pong"                        // string → text content
run: async ({ id }) => ({ name: "Sam" })       // object → JSON text content
run: async () => { throw new Error("oops") }   // throw → isError result

Test tools without a server

server.call() invokes a tool directly — no MCP protocol, no client needed. Use it in unit tests.

const server = createServer({ tools: [getUser], auth: { ... } });

const result = await server.call("get_user", { id: "123" });
console.log(result.content[0].text); // the tool's output

Wire into Claude Desktop

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

Full API

defineTool(definition)

| Field | Type | Description | |---|---|---| | name | string | Tool name shown to the AI | | description | string | What the tool does — the AI uses this to decide when to call it | | input | z.ZodObject | Zod schema for the tool's arguments. Use .describe() on fields for per-param descriptions | | run | (args, ctx) => unknown | Tool handler. Return any value; throw to signal failure |

createServer(options)

| Field | Type | Description | |---|---|---| | tools | ToolDefinition[] | Tools to expose | | auth | AuthConfig? | Auth injected into ctx.headers in every tool | | name | string? | Server name shown to MCP clients. Default: "mcp-server" | | version | string? | Server version. Default: "1.0.0" |

MCPServer

| Method | Description | |---|---| | .listen() | Start the server over stdio | | .call(toolName, args?) | Call a tool directly — useful for testing |


How it compares

| | mcp-sdk | mcp-remote | @modelcontextprotocol/sdk | |---|---|---|---| | Role | Consume + build MCP servers | CLI proxy only | Build servers/clients (low-level) | | Single-server proxy | ✓ | ✓ | manual | | Multi-server aggregation | ✓ | ✗ | ✗ | | Middleware (log, cache, retry) | ✓ | ✗ | ✗ | | React / Next.js / Express | ✓ | ✗ | ✗ | | Build a server (Zod tools) | ✓ | ✗ | boilerplate | | inspect and call commands | ✓ | ✗ | ✗ | | OAuth | soon | ✓ | manual |


Requirements

  • Node.js 18+
  • Zod 3 or 4 (only required for mcp-sdk/server)

License

MIT