Reusable MCP primitives and utilities for programaticaly building custom Model Context Protocol servers and tools with JavaScript and TypeScript.
- ⚙️ TypeScript-first development with built-in type safety
- 🐛 Easy debugging with MCP Inspector
npm install @sh41/mcp-tools @modelcontextprotocol/sdk@^1.18 zod@^3.25This package requires and assumes you already installed:
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.18",
"zod": "^3.25"
},[!INFO]
zodis intentionaly downgraded from^4, seeDECISIONS.mdfor more details.
// index.ts
import { buildMCPServer } from "@sh41/mcp-utils"
import { buildLogger } from "@asd14/node-utils/logger"
import { calculatorTool } from "./tools/calculator.js"
const logger = buildLogger({ namespace: "hello-mcp-world", level: "info" })
// Create server instance
const server = buildMCPServer({
name: "hello-mcp-world",
version: "1.0.0",
tools: [calculatorTool],
transport: "http"
})
// Set up event handlers
server.events.on("serverStarting", () => {
logger.info("Starting MCP server…")
})
server.events.on("serverReady", (connectionInfo, registeredTools) => {
logger.success("MCP server ready", {
tools: registeredTools.map(tool => tool.name),
...connectionInfo
})
if (connectionInfo.transport === "http") {
logger.tadaa({
title: "MCP Server Running",
message: `http://${connectionInfo.host}:${connectionInfo.port}/mcp`,
note: "Connect your MCP client to this endpoint to extend your LLM's capabilities"
})
}
})
// Start server
await server.start()
// Graceful shutdown
const shutdown = async () => {
logger.info("Shutting down gracefully…")
await server.stop()
process.exit(0)
}
process.on("SIGTERM", () => {
void shutdown()
})// tools/calculator.ts
import type { Tool, ToolHandler } from "@sh41/mcp-utils"
import { z } from "zod"
/**
* Schema defining the input parameters for calculator operations
*/
const inputSchema = {
operation: z
.enum(["add", "subtract", "multiply", "divide"] as const)
.describe("Operation to perform"),
a: z.number().describe("First number"),
b: z.number().describe("Second number")
}
/**
* Schema defining the output format of calculator operations
*/
const outputSchema = {
operation: z.string().describe("Operation performed"),
parameters: z
.array(z.number())
.describe("Parameters used in the calculation"),
result: z.string().describe("Result of the calculation")
}
/**
* Implements the arithmetic operations supported by the calculator
*/
const capabilities = {
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
multiply: (a: number, b: number) => a * b,
divide: (a: number, b: number) => {
if (b === 0) throw new Error("Division by zero is not allowed")
return a / b
}
} as const
/**
* Calculator handler implementation
*/
const handler: ToolHandler<typeof inputSchema> = ({ operation, a, b }) => {
try {
const result = String(capabilities[operation](a, b))
return {
structuredContent: {
operation,
parameters: [a, b],
result: result
},
content: [
{
type: "text",
text: `Successfully performed '${operation}' operation on '${a}' and '${b}'`
},
{
type: "text",
text: `The result is ${result}`
}
]
}
} catch (error: unknown) {
throw new Error(`Calculation error: ${(error as Error).message}`)
}
}
/**
* Calculator tool that performs basic arithmetic operations
*
* @example
* calculator.action({ operation: "add", a: 5, b: 3 }) // Returns { answer: "8" }
* calculator.action({ operation: "multiply", a: 4, b: 2 }) // Returns { answer: "8" }
*/
const calculatorTool: Tool<typeof inputSchema, typeof outputSchema> = {
name: "calculator",
description:
"Performs basic arithmetic operations: add, subtract, multiply, and divide",
inputSchema,
outputSchema,
handler
}
export { calculatorTool }Factory function for building an MCP server instance.
Options:
name- Display nameversion- Semantic versiondescription?- Optional summary of the server capabilitiestools?- Optional initial tools to register with the servertransport- Communication transport method:stdio: Standard input/output for direct process communicationhttp: HTTP server for web-based communication
port?- Optional HTTP port number. If not provided, a free random port is assigned
registerTools(tools)- Register toolsstart()- Start the serverstop()- Stop the server gracefullyrestart()- Restart the serverisRunning()- Check if server is runninggetStatus()- Get server status and info
Access via server.events.on(event, handler):
serverStarting- Server beginning startupserverReady- Server ready with connection info and toolsserverStartError- Startup failed with errorserverStopping- Server beginning shutdownserverStopped- Server completely stoppedrequestReceived- HTTP request received (HTTP transport only)requestFailed- Request processing failedrequestCompleted- Request processing completed
Tool<TInput, TOutput>- Typed tool definitionToolHandler<TInput>- Tool handler function typeAnyTool- Type-erased tool for arrays and eventsMcpServerInstance- Server instance typeMcpServerOptions- Server configuration typeMcpServerEvents- Event definitions
This package works standalone or in an NPM workspace. The --no-workspaces
flag in build scripts ensures package-lock.json reflects this package's
dependencies regardless of context.
# Standalone
git clone https://github.com/shell41/mcp-utils.git \
&& cd mcp-utils \
&& npm install
# Monorepo
cd monorepo-root \
&& git submodule add https://github.com/shell41/mcp-utils.git packages/mcp-utils \
&& npm installMIT
