by aashari
Provides a production‑ready foundation for building custom Model Context Protocol servers in TypeScript, featuring IP geolocation tools, a full CLI, dual STDIO/HTTP transports, TOON output format, JMESPath filtering, and an extensible 5‑layer architecture.
The project delivers a ready‑to‑use TypeScript boilerplate that implements the Model Context Protocol (MCP) specification. It includes a complete layered architecture (CLI, tools, resources, controllers, services, utils) and a working example for IP address lookup, enabling rapid creation of new MCP integrations.
npx -y @aashari/boilerplate-mcp-server
TRANSPORT_MODE=stdio node dist/index.jsTRANSPORT_MODE=http node dist/index.jsnpm run mcp:stdio or npm run mcp:http.npm run cli -- get-ip-details 8.8.8.8 --output-format json
src/index.ts.--jq) filtering for selective field extraction.Q: Do I need an API key for the IP lookup example?
A: The free tier works without a key (HTTP only). For extended data (ASN, mobile detection) set IPAPI_API_TOKEN in the environment.
Q: How do I switch between STDIO and HTTP transports?
A: Set the TRANSPORT_MODE environment variable to stdio or http before starting the server.
Q: Can I add my own tool without modifying core files?
A: Yes. Create a new file under src/tools/ with a Zod schema, implement the handler, and register it via server.registerTool in the entry point.
Q: What is the recommended way to run the server in production?
A: Build the TypeScript sources (npm run build) and start the compiled entry point with the desired transport mode, optionally using a process manager like PM2.
Q: Is the project compatible with Node.js 18+ only? A: Yes, the minimum supported version is Node.js 18.
A production-ready foundation for developing custom Model Context Protocol (MCP) servers in TypeScript. Provides a complete layered architecture pattern, working example implementation, and comprehensive developer infrastructure to connect AI assistants with external APIs and data sources.
registerTool API patternModel Context Protocol (MCP) is an open standard for securely connecting AI systems to external tools and data sources. This boilerplate implements the MCP specification with a clean, layered architecture that can be extended to build custom MCP servers for any API or data source.
# Clone the repository
git clone https://github.com/aashari/boilerplate-mcp-server.git
cd boilerplate-mcp-server
# Install dependencies
npm install
# Build the project
npm run build
# Run in different modes:
# 1. CLI Mode - Execute commands directly
npm run cli -- get-ip-details 8.8.8.8
npm run cli -- get-ip-details # Get your current IP
npm run cli -- get-ip-details 1.1.1.1 --include-extended-data
npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}" # JQ filter
npm run cli -- get-ip-details 8.8.8.8 --output-format json # JSON output
# 2. STDIO Transport - For AI assistant integration (Claude Desktop, Cursor)
npm run mcp:stdio
# 3. HTTP Transport - For web-based integrations
npm run mcp:http
# 4. Development with MCP Inspector
npm run mcp:inspect # Auto-opens browser with debugging UI
TRANSPORT_MODE=stdio node dist/index.jsPORT env var)http://localhost:3000/mcphttp://localhost:3000/ → Returns server versionTRANSPORT_MODE=http node dist/index.jsTOON (Token-Oriented Object Notation) is a human-readable format optimized for LLMs, reducing token usage by 30-60% compared to JSON:
status: success
query: 8.8.8.8
country: United States
city: Ashburn
lat: 39.03
lon: -77.5
Standard JSON output when --output-format json is specified:
{
"status": "success",
"query": "8.8.8.8",
"country": "United States",
"city": "Ashburn"
}
Use --jq to extract only needed fields, reducing token costs:
# Extract specific fields
npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}"
# Output:
# ip: 8.8.8.8
# country: United States
# Nested structure
npm run cli -- get-ip-details 8.8.8.8 --jq "{location: {city: city, coords: {lat: lat, lon: lon}}}"
See JMESPath documentation for more filter examples.
src/
├── cli/ # Command-line interfaces
│ ├── index.ts # CLI entry point with Commander setup
│ └── ipaddress.cli.ts # IP address CLI commands
├── controllers/ # Business logic orchestration
│ ├── ipaddress.controller.ts # IP lookup business logic
│ └── ipaddress.formatter.ts # Response formatting
├── services/ # External API interactions
│ ├── vendor.ip-api.com.service.ts # ip-api.com service
│ └── vendor.ip-api.com.types.ts # Service type definitions
├── tools/ # MCP tool definitions (AI interface)
│ ├── ipaddress.tool.ts # IP lookup tool for AI assistants
│ └── ipaddress.types.ts # Tool argument schemas
├── resources/ # MCP resource definitions
│ └── ipaddress.resource.ts # IP lookup resource (URI: ip://address)
├── types/ # Global type definitions
│ └── common.types.ts # Shared interfaces (ControllerResponse, etc.)
├── utils/ # Shared utilities
│ ├── logger.util.ts # Contextual logging system
│ ├── error.util.ts # MCP-specific error formatting
│ ├── error-handler.util.ts # Error handling utilities
│ ├── config.util.ts # Environment configuration
│ ├── constants.util.ts # Version and package constants
│ ├── formatter.util.ts # Markdown formatting
│ ├── toon.util.ts # TOON format encoding
│ ├── jq.util.ts # JMESPath filtering
│ └── transport.util.ts # HTTP transport utilities
└── index.ts # Server entry point (dual transport)
The boilerplate follows a clean, layered architecture that promotes maintainability and clear separation of concerns:
src/cli/)get-ip-details [ipAddress] --include-extended-data --no-use-httpssrc/tools/)ip_get_details tool with optional IP address and configuration optionssrc/resources/)registerResource API with ResourceTemplate for parameterized URIsip://{ipAddress} resource template providing IP geolocation datasrc/controllers/)src/services/)src/utils/)logger.util.ts: Contextual logging (file:method context)error.util.ts: MCP-specific error formattingtransport.util.ts: HTTP/API utilities with retry logicconfig.util.ts: Environment configuration managementtoon.util.ts: TOON format encoding (token-efficient output)jq.util.ts: JMESPath filtering for response transformation# Build and Clean
npm run build # Build TypeScript to dist/
npm run clean # Remove dist/ and coverage/
npm run prepare # Build + ensure executable permissions (for npm publish)
# CLI Testing
npm run cli -- get-ip-details 8.8.8.8 # Test specific IP
npm run cli -- get-ip-details --include-extended-data # Test with extended data
npm run cli -- get-ip-details --no-use-https # Test with HTTP
# MCP Server Modes
npm run mcp:stdio # STDIO transport for AI assistants
npm run mcp:http # HTTP transport on port 3000
npm run mcp:inspect # HTTP + auto-open MCP Inspector
# Development with Debugging
npm run dev:stdio # STDIO with MCP Inspector integration
npm run dev:http # HTTP with debug logging enabled
# Testing
npm test # Run all tests (Jest)
npm run test:coverage # Generate coverage report
npm run test:cli # Run CLI-specific tests
# Code Quality
npm run lint # ESLint with TypeScript rules
npm run format # Prettier formatting
npm run update:deps # Update dependencies
TRANSPORT_MODE: Transport mode (stdio | http, default: stdio)PORT: HTTP server port (default: 3000)DEBUG: Enable debug logging (true | false, default: false)IPAPI_API_TOKEN: API token for ip-api.com extended data (optional, free tier available).env File# Basic configuration
TRANSPORT_MODE=http
PORT=3001
DEBUG=true
# Extended data (requires ip-api.com account)
IPAPI_API_TOKEN=your_token_here
MCP Inspector: Visual tool for testing your MCP tools
npm run mcp:inspectDebug Logging: Enable with DEBUG=true environment variable
Create ~/.mcp/configs.json:
{
"boilerplate": {
"environments": {
"DEBUG": "true",
"TRANSPORT_MODE": "http",
"PORT": "3000"
}
}
}
Create a new service in src/services/ following the vendor-specific naming pattern:
// src/services/vendor.example-api.service.ts
import { Logger } from '../utils/logger.util.js';
import { fetchApi } from '../utils/transport.util.js';
import { ExampleApiResponse, ExampleApiRequestOptions } from './vendor.example-api.types.js';
import { createApiError, McpError } from '../utils/error.util.js';
const serviceLogger = Logger.forContext('services/vendor.example-api.service.ts');
async function get(
param?: string,
options: ExampleApiRequestOptions = {}
): Promise<ExampleApiResponse> {
const methodLogger = serviceLogger.forMethod('get');
methodLogger.debug(`Calling Example API with param: ${param}`);
try {
const url = `https://api.example.com/${param || 'default'}`;
const rawData = await fetchApi<ExampleApiResponse>(url, {
headers: options.apiKey ? { 'Authorization': `Bearer ${options.apiKey}` } : {}
});
methodLogger.debug('Received successful response from Example API');
return rawData;
} catch (error) {
methodLogger.error('Service error fetching data', error);
if (error instanceof McpError) {
throw error;
}
throw createApiError(
'Unexpected service error while fetching data',
undefined,
error
);
}
}
export default { get };
Add a controller in src/controllers/ to handle business logic with error context:
// src/controllers/example.controller.ts
import { Logger } from '../utils/logger.util.js';
import exampleService from '../services/vendor.example-api.service.js';
import { formatExample } from './example.formatter.js';
import { handleControllerError, buildErrorContext } from '../utils/error-handler.util.js';
import { ControllerResponse } from '../types/common.types.js';
import { config } from '../utils/config.util.js';
const logger = Logger.forContext('controllers/example.controller.ts');
export interface GetDataOptions {
param?: string;
includeMetadata?: boolean;
}
async function getData(
options: GetDataOptions = {}
): Promise<ControllerResponse> {
const methodLogger = logger.forMethod('getData');
methodLogger.debug(`Getting data for param: ${options.param || 'default'}`, options);
try {
// Apply business logic and defaults
const apiKey = config.get('EXAMPLE_API_TOKEN');
// Call service layer
const data = await exampleService.get(options.param, {
apiKey,
includeMetadata: options.includeMetadata ?? false
});
// Format response
const formattedContent = formatExample(data);
return { content: formattedContent };
} catch (error) {
throw handleControllerError(
error,
buildErrorContext(
'ExampleData',
'getData',
'controllers/example.controller.ts@getData',
options.param || 'default',
{ options }
)
);
}
}
export default { getData };
Create a tool definition in src/tools/ following the registration pattern:
// src/tools/example.tool.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { Logger } from '../utils/logger.util.js';
import { formatErrorForMcpTool } from '../utils/error.util.js';
import exampleController from '../controllers/example.controller.js';
const logger = Logger.forContext('tools/example.tool.ts');
// Define Zod schema for tool arguments
const GetDataSchema = z.object({
param: z.string().optional().describe('Optional parameter for the API call'),
includeMetadata: z.boolean().optional().default(false)
.describe('Whether to include additional metadata in the response')
});
async function handleGetData(args: Record<string, unknown>) {
const methodLogger = logger.forMethod('handleGetData');
try {
methodLogger.debug('Tool example_get_data called', args);
// Validate arguments with Zod
const validatedArgs = GetDataSchema.parse(args);
// Call controller
const result = await exampleController.getData({
param: validatedArgs.param,
includeMetadata: validatedArgs.includeMetadata
});
// Return MCP-formatted response
return {
content: [
{
type: 'text' as const,
text: result.content
}
]
};
} catch (error) {
methodLogger.error('Tool example_get_data failed', error);
return formatErrorForMcpTool(error);
}
}
// Registration function using the modern registerTool API (SDK v1.22.0+)
function registerTools(server: McpServer) {
const registerLogger = logger.forMethod('registerTools');
registerLogger.debug('Registering example tools...');
// SDK best practices: 'title' for UI display name, 'description' for detailed info
server.registerTool(
'example_get_data',
{
title: 'Get Example Data', // Display name for UI (e.g., 'Get Example Data')
description: `Gets data from the Example API with optional parameter.
Use this tool to fetch example data. Returns formatted data as Markdown.`,
inputSchema: GetDataSchema,
},
handleGetData
);
registerLogger.debug('Example tools registered successfully');
}
export default { registerTools };
Create a CLI command in src/cli/ following the Commander pattern:
// src/cli/example.cli.ts
import { Command } from 'commander';
import { Logger } from '../utils/logger.util.js';
import exampleController from '../controllers/example.controller.js';
import { handleCliError } from '../utils/error.util.js';
const logger = Logger.forContext('cli/example.cli.ts');
function register(program: Command) {
const methodLogger = logger.forMethod('register');
methodLogger.debug('Registering example CLI commands...');
program
.command('get-data')
.description('Gets data from the Example API')
.argument('[param]', 'Optional parameter for the API call')
.option('-m, --include-metadata', 'Include additional metadata in response')
.action(async (param, options) => {
const actionLogger = logger.forMethod('action:get-data');
try {
actionLogger.debug('CLI get-data called', { param, options });
const result = await exampleController.getData({
param,
includeMetadata: options.includeMetadata || false
});
console.log(result.content);
} catch (error) {
handleCliError(error);
}
});
methodLogger.debug('Example CLI commands registered successfully');
}
export default { register };
Update the entry points to register your new components:
// 1. Register CLI in src/cli/index.ts
import exampleCli from './example.cli.js';
export async function runCli(args: string[]) {
// ... existing setup code ...
// Register CLI commands
exampleCli.register(program); // Add this line
// ... rest of function
}
// 2. Register Tools in src/index.ts
import exampleTools from './tools/example.tool.js';
// In the startServer function, after existing registrations:
exampleTools.registerTools(serverInstance);
Create a resource in src/resources/ using the modern registerResource API:
// src/resources/example.resource.ts
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Logger } from '../utils/logger.util.js';
import exampleController from '../controllers/example.controller.js';
import { formatErrorForMcpResource } from '../utils/error.util.js';
const logger = Logger.forContext('resources/example.resource.ts');
function registerResources(server: McpServer) {
const registerLogger = logger.forMethod('registerResources');
registerLogger.debug('Registering example resources...');
// Use registerResource with ResourceTemplate for parameterized URIs (SDK v1.22.0+)
server.registerResource(
'example-data',
new ResourceTemplate('example://{param}', { list: undefined }),
{
title: 'Example Data', // Display name for UI
description: 'Retrieve example data by parameter'
},
async (uri, variables) => {
const methodLogger = logger.forMethod('exampleResource');
try {
// Extract parameter from template variables
const param = variables.param as string | undefined;
methodLogger.debug('Example resource called', { uri: uri.href, param });
const result = await exampleController.getData({ param });
return {
contents: [
{
uri: uri.href,
text: result.content,
mimeType: 'text/markdown'
}
]
};
} catch (error) {
methodLogger.error('Resource error', error);
return formatErrorForMcpResource(error, uri.href);
}
}
);
registerLogger.debug('Example resources registered successfully');
}
export default { registerResources };
The boilerplate includes a complete IP address geolocation example demonstrating all layers:
CLI Commands:
npm run cli -- get-ip-details # Get current public IP (TOON format)
npm run cli -- get-ip-details 8.8.8.8 # Get details for specific IP
npm run cli -- get-ip-details 1.1.1.1 --include-extended-data # With extended data
npm run cli -- get-ip-details 8.8.8.8 --no-use-https # Force HTTP (for free tier)
npm run cli -- get-ip-details 8.8.8.8 --output-format json # JSON output
npm run cli -- get-ip-details 8.8.8.8 --jq "{ip: query, country: country}" # Filtered output
MCP Tools:
ip_get_details - IP geolocation lookup for AI assistants
outputFormat: "toon" (default) or "json"jq: JMESPath expression for filteringMCP Resources:
ip://{ipAddress} - IP details resource template (e.g., ip://8.8.8.8)# Optional - for extended data features
IPAPI_API_TOKEN=your_token_from_ip-api.com
# Development
DEBUG=true # Enable detailed logging
TRANSPORT_MODE=http # Use HTTP transport
PORT=3001 # Custom port
Customize Package Details:
{
"name": "your-mcp-server-name",
"version": "1.0.0",
"description": "Your custom MCP server",
"author": "Your Name",
"keywords": ["mcp", "your-domain", "ai-integration"]
}
Update Documentation: Replace IP address examples with your use case
Test Thoroughly:
npm run build && npm test
npm run cli -- your-command
npm run mcp:stdio # Test with MCP Inspector
Publish: npm publish (requires npm login)
The boilerplate includes comprehensive testing infrastructure:
tests/ # Not present - tests are in src/
src/
├── **/*.test.ts # Co-located with source files
├── utils/ # Utility function tests
├── controllers/ # Business logic tests
├── services/ # API integration tests
└── cli/ # CLI command tests
*.util.test.ts)npm test # Run all tests
npm run test:coverage # Generate coverage report
npm run test:cli # CLI-specific tests only
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by modelcontextprotocol
A Model Context Protocol server for Git repository interaction and automation.
by zed-industries
A high‑performance, multiplayer code editor designed for speed and collaboration.
by modelcontextprotocol
Model Context Protocol Servers
by modelcontextprotocol
A Model Context Protocol server that provides time and timezone conversion capabilities.
by cline
An autonomous coding assistant that can create and edit files, execute terminal commands, and interact with a browser directly from your IDE, operating step‑by‑step with explicit user permission.
by upstash
Provides up-to-date, version‑specific library documentation and code examples directly inside LLM prompts, eliminating outdated information and hallucinated APIs.
by daytonaio
Provides a secure, elastic infrastructure that creates isolated sandboxes for running AI‑generated code with sub‑90 ms startup, unlimited persistence, and OCI/Docker compatibility.
by continuedev
Enables faster shipping of code by integrating continuous AI agents across IDEs, terminals, and CI pipelines, offering chat, edit, autocomplete, and customizable agent workflows.
by github
Connects AI tools directly to GitHub, enabling natural‑language interactions for repository browsing, issue and pull‑request management, CI/CD monitoring, code‑security analysis, and team collaboration.
{
"mcpServers": {
"boilerplate-mcp-server": {
"command": "npx",
"args": [
"-y",
"@aashari/boilerplate-mcp-server"
],
"env": {
"IPAPI_API_TOKEN": "<YOUR_API_KEY>"
}
}
}
}claude mcp add boilerplate-mcp-server npx -y @aashari/boilerplate-mcp-server