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.
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 (
NodeNextmodule mode) @modelcontextprotocol/sdk— official MCP TypeScript SDK- Zod — input/output schema validation
- Express — HTTP server for Streamable HTTP transport
openweather-api-node— OpenWeatherMap API clientdotenv— 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 sessionexpress.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
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。