node-mcp

node-mcp

An MCP server that offers arithmetic operations (addition and division) and current weather data from OpenWeatherMap. It serves as a learning project for building MCP servers with Node.js and TypeScript, supporting both stdio and Streamable HTTP transports.

Category
访问服务器

README

node-mcp

A training project for building MCP (Model Context Protocol) servers with Node.js and TypeScript.

Stack

  • Node.js with ES modules ("type": "module")
  • TypeScript (NodeNext module mode)
  • @modelcontextprotocol/sdk — official MCP TypeScript SDK
  • Zod — input/output schema validation
  • Express — HTTP server for Streamable HTTP transport
  • openweather-api-node — OpenWeatherMap API client
  • dotenv — environment variable loading

Project Structure

node-mcp/
├── index.ts                        # Server entry point — registers all tools, selects transport
├── toolHandler.ts                  # Generic error-handling wrapper for tool handlers
├── tools/
│   ├── opperations/
│   │   ├── add.ts                  # Add tool
│   │   └── divide.ts               # Divide tool
│   └── weather/
│       └── weather.ts              # Weather tool
├── types.d.ts                      # Module declarations for untyped packages
├── .env                            # API keys and config (not committed)
├── dist/                           # Compiled output (generated by tsc)
├── tsconfig.json
└── package.json

Getting Started

pnpm install

Create a .env file in the project root:

OPEN_WEATHER_API_KEY=your_key_here
NODE_ENV=development
TRANSPORT=stdio        # or "http"
MCP_PORT=3000          # optional, HTTP mode only

Build:

pnpm build

Running the Server

stdio mode (for Claude Desktop / Claude Code)

$env:TRANSPORT="stdio"; node dist/index.js

The server communicates over stdin/stdout and waits for JSON-RPC messages from an MCP client. No visible output when idle.

HTTP mode (persistent process)

$env:TRANSPORT="http"; node dist/index.js

Starts an Express server on port 3000 (or MCP_PORT):

Starting Express MCP server on port 3000
MCP endpoint: http://localhost:3000/mcp
Server is running on http://localhost:3000

Testing with MCP Inspector

stdio mode

pnpm exec mcp-inspector node dist/index.js

HTTP mode

Start the server first, then open the inspector without arguments:

pnpm exec mcp-inspector

In the inspector UI, set transport type to Streamable HTTP and URL to http://localhost:3000/mcp.

Connecting to Claude Code

stdio (project-level)

claude mcp add --scope project node-mcp-server node C:/Training/node-mcp/dist/index.js

HTTP

Add the server URL directly in Claude Code's MCP settings pointing at http://localhost:3000/mcp.

Tools

add

Adds two numbers together and returns the result.

Inputs

Name Type Description
numOne number First operand
numTwo number Second operand

Output

{ "result": "32 + 32 = 64" }

divide

Divides two numbers and returns the result. Both inputs must be positive — validated by Zod before the handler runs.

Inputs

Name Type Description
numOne number (positive) Dividend
numTwo number (positive) Divisor (cannot be zero)

Output

{ "result": "10 / 2 = 5" }

weather

Returns current weather data for a location using the OpenWeatherMap API. Results are in metric units.

Supply one of the following location strategies:

Name Type Description
lat number (-90 to 90) Latitude (use with lng)
lng number (-180 to 180) Longitude (use with lat)
locationName string City or place name (e.g. "London")
zipCode string Zip/postal code (e.g. "90210")

Priority order when multiple are provided: coordinates → zip code → location name. At least one strategy must be supplied or the tool returns an error.

Output

Returns a JSON object with temperature, humidity, wind speed, weather description, and more.

Requires OPEN_WEATHER_API_KEY in .env.

Key Concepts

Transport selection

The server supports two transports, selected via the TRANSPORT environment variable:

Value Use case
stdio Local servers spawned as child processes (Claude Code, Claude Desktop)
http Persistent process accessible over a network

Tools are registered once and shared between both transports — the capability is decoupled from the delivery mechanism.

Tool registration pattern

Each tool lives in its own file and exports a registration function that accepts the server instance:

export const myTool = (server: McpServer) => {
  server.registerTool('tool-name', { ... }, toolHandler(async (inputs) => {
    // handler logic
  }))
}

In index.ts:

myTool(server)

Error handling — toolHandler

All tool handlers are wrapped with toolHandler, a generic wrapper that catches any thrown error and returns it in the correct MCP format:

export const toolHandler = <T>(fn: (inputs: T) => Promise<CallToolResult>) => {
  return async (inputs: T) => {
    try {
      return await fn(inputs)
    } catch (err) {
      return {
        isError: true,
        content: [{
          type: 'text' as const,
          text: err instanceof Error
            ? process.env.NODE_ENV === 'development' ? err.stack ?? err.message : err.message
            : 'Something went wrong...'
        }]
      }
    }
  }
}
  • In development (NODE_ENV=development): returns the full stack trace
  • In production: returns the error message only
  • For non-Error throws: returns a generic fallback message

Input validation with Zod

Zod schemas on inputSchema are validated by the SDK before your handler runs. Invalid inputs return a -32602 protocol error — they never reach your handler:

inputSchema: {
  numTwo: z.number().positive()            // rejects zero and negatives
  numTwo: z.number().refine(n => n !== 0)  // rejects only zero
}

Streamable HTTP transport

The HTTP transport uses Express with the SDK's StreamableHTTPServerTransport. Key points:

  • sessionIdGenerator: randomUUID — required, assigns a unique ID to each client session
  • express.json() middleware parses the request body before it reaches the transport
  • The parsed body is passed as the third argument to handleRequest — the SDK uses it directly rather than re-parsing the raw request
app.use(express.json())
app.all('/mcp', (req, res) => {
  transport.handleRequest(req, res, req.body)
})

ES module import paths

With "module": "NodeNext" in tsconfig, imports require explicit .js extensions even in .ts source files:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'

Environment variables

Loaded via import 'dotenv/config' at the top of index.ts. Add .env to .gitignore to avoid committing API keys.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选
mcp-server-qdrant

mcp-server-qdrant

这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。

官方
精选
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选