TypeScript Model Context Protocol (MCP) server boilerplate providing IP lookup tools/resources. Includes CLI support and extensible structure for connecting AI systems (LLMs) to external data sources
A foundation for developing custom Model Context Protocol (MCP) servers in TypeScript. Provides a complete layered architecture pattern, working example tools, and developer infrastructure to connect AI assistants with external APIs and data sources.
Model 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
# Run in different modes:
# 1. CLI Mode - Execute commands directly
npm run cli -- get-ip-details 8.8.8.8
# 2. STDIO Transport - For direct AI assistant integration
npm run mcp:stdio
# 3. HTTP Transport - For web-based integrations (default)
npm run mcp:http
# 4. Development with MCP Inspector
npm run mcp:inspect
TRANSPORT_MODE=stdio npm run build && node dist/index.js
http://localhost:3000/mcp
http://localhost:3000/
TRANSPORT_MODE=http npm run build && node dist/index.js
src/
├── cli/ # Command-line interfaces
│ ├── index.ts # CLI entry point
│ └── *.cli.ts # Feature-specific CLI modules
├── controllers/ # Business logic
│ └── *.controller.ts # Feature controllers
├── services/ # External API interactions
│ └── *.service.ts # Service modules
├── tools/ # MCP tool definitions
│ ├── *.tool.ts # Tool implementations
│ └── *.types.ts # Tool argument schemas
├── resources/ # MCP resource definitions
│ └── *.resource.ts # Resource implementations
├── types/ # Type definitions
│ └── common.types.ts # Shared type definitions
├── utils/ # Shared utilities
│ ├── logger.util.ts # Structured logging
│ ├── error.util.ts # Error handling
│ └── ... # Other utility modules
└── index.ts # Server entry point
</details>
The boilerplate follows a clean, layered architecture that promotes maintainability and clear separation of concerns:
src/cli/*.cli.ts
)commander
for argument parsing, call controllers, handle errors with handleCliError
<feature>.cli.ts
src/tools/*.tool.ts
)zod
for schema validation, call controllers, format responses for MCP<feature>.tool.ts
with types in <feature>.types.ts
src/controllers/*.controller.ts
)ControllerResponse
objects, throw errors with context<feature>.controller.ts
with optional <feature>.formatter.ts
src/services/*.service.ts
)<feature>.service.ts
or vendor.<vendor>.<feature>.service.ts
src/utils/*.util.ts
)# Development
npm run build # Build TypeScript
npm run clean # Clean build artifacts
# Running different modes
npm run cli -- [command] # Run CLI commands
npm run mcp:stdio # Run with STDIO transport
npm run mcp:http # Run with HTTP transport (default)
npm run mcp:inspect # Run with MCP Inspector
# Development modes
npm run dev:stdio # STDIO with inspector
npm run dev:http # HTTP in development mode
# Testing
npm test # Run all tests
npm run test:coverage # Generate coverage report
# Code Quality
npm run lint # Run ESLint
npm run format # Format with Prettier
TRANSPORT_MODE
: Set to stdio
or http
(default: http
)PORT
: HTTP server port (default: 3000
)DEBUG
: Enable debug logging (default: false
)IPAPI_API_TOKEN
: API token for ip-api.com (optional)npm run mcp:inspect
DEBUG=true
environment variableCreate ~/.mcp/configs.json
:
{
"boilerplate": {
"environments": {
"DEBUG": "true",
"TRANSPORT_MODE": "http",
"PORT": "3000"
}
}
}
</details>
Create a new service in src/services/
to interact with your external API:
// src/services/example.service.ts
import { Logger } from '../utils/logger.util.js';
const logger = Logger.forContext('services/example.service.ts');
export async function getData(param: string): Promise<any> {
logger.debug('Getting data', { param });
// API interaction code here
return { result: 'example data' };
}
Add a controller in src/controllers/
to handle business logic:
// src/controllers/example.controller.ts
import { Logger } from '../utils/logger.util.js';
import * as exampleService from '../services/example.service.js';
import { formatMarkdown } from '../utils/formatter.util.js';
import { handleControllerError } from '../utils/error-handler.util.js';
import { ControllerResponse } from '../types/common.types.js';
const logger = Logger.forContext('controllers/example.controller.ts');
export interface GetDataOptions {
param?: string;
}
export async function getData(
options: GetDataOptions = {},
): Promise<ControllerResponse> {
try {
logger.debug('Getting data with options', options);
const data = await exampleService.getData(options.param || 'default');
const content = formatMarkdown(data);
return { content };
} catch (error) {
throw handleControllerError(error, {
entityType: 'ExampleData',
operation: 'getData',
source: 'controllers/example.controller.ts',
});
}
}
Create a tool definition in src/tools/
:
// 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 * as exampleController from '../controllers/example.controller.js';
const logger = Logger.forContext('tools/example.tool.ts');
const GetDataArgs = z.object({
param: z.string().optional().describe('Optional parameter'),
});
type GetDataArgsType = z.infer<typeof GetDataArgs>;
async function handleGetData(args: GetDataArgsType) {
try {
logger.debug('Tool get_data called', args);
const result = await exampleController.getData({
param: args.param,
});
return {
content: [{ type: 'text' as const, text: result.content }],
};
} catch (error) {
logger.error('Tool get_data failed', error);
return formatErrorForMcpTool(error);
}
}
export function register(server: McpServer) {
server.tool(
'get_data',
`Gets data from the example API, optionally using \`param\`.
Use this to fetch example data. Returns formatted data as Markdown.`,
GetDataArgs.shape,
handleGetData,
);
}
Create a CLI command in src/cli/
:
// src/cli/example.cli.ts
import { program } from 'commander';
import { Logger } from '../utils/logger.util.js';
import * as exampleController from '../controllers/example.controller.js';
import { handleCliError } from '../utils/error-handler.util.js';
const logger = Logger.forContext('cli/example.cli.ts');
program
.command('get-data')
.description('Get example data')
.option('--param <value>', 'Optional parameter')
.action(async (options) => {
try {
logger.debug('CLI get-data called', options);
const result = await exampleController.getData({
param: options.param,
});
console.log(result.content);
} catch (error) {
handleCliError(error);
}
});
Update the entry points to register your new components:
// In src/cli/index.ts
import '../cli/example.cli.js';
// In src/index.ts (for the tool)
import exampleTool from './tools/example.tool.js';
// Then in registerTools function:
exampleTool.register(server);
</details>
{
"name": "your-mcp-server-name",
"version": "1.0.0",
"description": "Your custom MCP server",
"author": "Your Name",
// Other fields...
}
npm run build
npm run mcp:stdio
and npm run mcp:http
npm publish
aashari/boilerplate-mcp-server
March 22, 2025
July 3, 2025
TypeScript