JavaScript MCP Template
This guide walks you through using the official JavaScript template for building Model Context Protocol (MCP) servers.
Introduction
The JavaScript template provides a solid foundation for developing MCP servers using Node.js. It includes all the necessary boilerplate code and demonstrates best practices for building production-ready MCP applications.
Getting Started
Prerequisites
Before you begin, make sure you have:
- Node.js 16.0.0 or later installed
- npm or yarn package manager
- TRMX CLI installed (see Installation Guide)
Creating a New Project
Create a new MCP server using the JavaScript template:
# Using the TRMX CLI
trmx init my-mcp-server --template javascript
# Navigate to the project directory
cd my-mcp-server
# Install dependencies
npm install
# or
yarn
Project Structure
The JavaScript template creates the following directory structure:
my-mcp-server/
├── node_modules/
├── src/
│ ├── config/
│ │ ├── index.js # Configuration settings
│ │ └── logger.js # Logging configuration
│ ├── tools/
│ │ ├── calculator.js # Example calculator tool
│ │ └── index.js # Tool registration
│ ├── resources/
│ │ └── index.js # Resource registration
│ ├── prompts/
│ │ └── index.js # Prompt template registration
│ ├── middleware/
│ │ └── auth.js # Authentication middleware
│ ├── utils/
│ │ └── error-handler.js # Error handling utilities
│ └── index.js # Main entry point
├── tests/
│ ├── tools/
│ │ └── calculator.test.js # Tool unit tests
│ └── integration/
│ └── api.test.js # API integration tests
├── .env # Environment variables
├── .env.example # Example environment variables
├── .gitignore
├── package.json
├── README.md
└── nodemon.json # Nodemon configuration
Core Components
Server Configuration
The main server configuration is in src/config/index.js
:
// src/config/index.js
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
export const config = {
port: process.env.PORT || 3000,
environment: process.env.NODE_ENV || 'development',
logLevel: process.env.LOG_LEVEL || 'info',
auth: {
enabled: process.env.AUTH_ENABLED === 'true',
apiKey: process.env.API_KEY,
},
models: {
default: process.env.DEFAULT_MODEL || 'gpt-4',
apiKey: process.env.OPENAI_API_KEY,
},
};
Main Entry Point
The server's entry point is src/index.js
:
// src/index.js
import { MCPServer } from '@trmx/server';
import { config } from './config';
import { tools } from './tools';
import { resources } from './resources';
import { prompts } from './prompts';
import { logger } from './config/logger';
async function main() {
try {
// Create an MCP server
const server = new MCPServer({
port: config.port,
tools,
resources,
prompts,
logger,
});
// Start the server
await server.start();
logger.info(`MCP server running on port ${config.port}`);
} catch (error) {
logger.error('Failed to start MCP server:', error);
process.exit(1);
}
}
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
logger.error('Uncaught exception:', error);
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled promise rejection:', reason);
process.exit(1);
});
// Start the server
main();
Defining Tools
Create tools in the src/tools
directory:
// src/tools/calculator.js
export const calculatorTool = {
name: 'calculator',
description: 'Performs basic arithmetic operations',
parameters: {
type: 'object',
properties: {
operation: {
type: 'string',
enum: ['add', 'subtract', 'multiply', 'divide'],
description: 'The operation to perform',
},
a: {
type: 'number',
description: 'First operand',
},
b: {
type: 'number',
description: 'Second operand',
},
},
required: ['operation', 'a', 'b'],
},
execute: async ({ operation, a, b }) => {
switch (operation) {
case 'add': return { result: a + b };
case 'subtract': return { result: a - b };
case 'multiply': return { result: a * b };
case 'divide':
if (b === 0) throw new Error('Division by zero');
return { result: a / b };
default:
throw new Error(`Unknown operation: ${operation}`);
}
},
};
Register tools in src/tools/index.js
:
// src/tools/index.js
import { calculatorTool } from './calculator';
export const tools = [
calculatorTool,
// Add more tools here
];
Adding Resources
Create resources in the src/resources
directory:
// src/resources/documents.js
export const documentsResource = {
name: 'documents',
description: 'Access to company documentation',
fetch: async (query) => {
// In a real implementation, you would search a database or other data source
const documents = [
{ id: 1, title: 'Getting Started', content: 'This guide helps you get started with our product...' },
{ id: 2, title: 'API Reference', content: 'Detailed API documentation for developers...' },
{ id: 3, title: 'Best Practices', content: 'Learn best practices for using our platform...' },
];
// Simple search implementation
const results = documents.filter(doc =>
doc.title.toLowerCase().includes(query.toLowerCase()) ||
doc.content.toLowerCase().includes(query.toLowerCase())
);
return results.map(doc => ({
id: doc.id.toString(),
title: doc.title,
content: doc.content,
}));
},
};
Register resources in src/resources/index.js
:
// src/resources/index.js
import { documentsResource } from './documents';
export const resources = [
documentsResource,
// Add more resources here
];
Prompt Templates
Create prompt templates in the src/prompts
directory:
// src/prompts/greeting.js
export const greetingPrompt = {
name: 'greeting',
description: 'A friendly greeting template',
template: `
You are a helpful assistant for {{company_name}}.
Always greet the user by their name, {{user_name}}, if available.
Be friendly but professional in your responses.
If the user asks about {{company_name}}'s products or services, provide helpful information.
`,
parameters: {
company_name: {
type: 'string',
description: 'The company name',
},
user_name: {
type: 'string',
description: 'The user\'s name',
required: false,
},
},
};
Register prompt templates in src/prompts/index.js
:
// src/prompts/index.js
import { greetingPrompt } from './greeting';
export const prompts = [
greetingPrompt,
// Add more prompts here
];
Authentication
The template includes basic API key authentication:
// src/middleware/auth.js
import { config } from '../config';
import { logger } from '../config/logger';
export function authenticate(req, res, next) {
// Skip authentication if disabled
if (!config.auth.enabled) {
return next();
}
// Get API key from header
const apiKey = req.headers['x-api-key'];
// Validate API key
if (!apiKey || apiKey !== config.auth.apiKey) {
logger.warn('Authentication failed: Invalid API key');
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
Running in Development Mode
The template is configured for easy development:
# Start development server with hot reloading
npm run dev
# or
yarn dev
This uses nodemon to automatically restart the server when files change.
Building for Production
Prepare your project for production:
# Build the project
npm run build
# or
yarn build
# Start in production mode
npm start
# or
yarn start
Testing
The template includes Jest for testing:
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
Example test for the calculator tool:
// tests/tools/calculator.test.js
import { calculatorTool } from '../../src/tools/calculator';
describe('Calculator Tool', () => {
test('adds numbers correctly', async () => {
const result = await calculatorTool.execute({
operation: 'add',
a: 5,
b: 3,
});
expect(result).toEqual({ result: 8 });
});
test('throws error for division by zero', async () => {
await expect(calculatorTool.execute({
operation: 'divide',
a: 10,
b: 0,
})).rejects.toThrow('Division by zero');
});
});
Deployment
Environment Variables
Before deployment, set up your environment variables:
# .env.example
PORT=3000
NODE_ENV=production
LOG_LEVEL=info
AUTH_ENABLED=true
API_KEY=your-api-key
OPENAI_API_KEY=your-openai-key
DEFAULT_MODEL=gpt-4
Deployment Options
1. TRMX AI Platform
Deploy to the TRMX AI platform:
# Login to TRMX AI
trmx login
# Deploy your MCP server
trmx deploy
2. Docker Deployment
The template includes a Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
Build and run the Docker container:
# Build Docker image
docker build -t my-mcp-server .
# Run Docker container
docker run -p 3000:3000 --env-file .env my-mcp-server
3. Cloud Platforms
For services like AWS, GCP, or Azure, use their respective deployment methods:
# Example for AWS Elastic Beanstalk
npm install -g eb-cli
eb init
eb create
eb deploy
Extending the Template
Adding a Database Connection
Install a database driver:
npm install mongoose
# or
yarn add mongoose
Create a database connection:
// src/config/database.js
import mongoose from 'mongoose';
import { config } from './index';
import { logger } from './logger';
export async function connectToDatabase() {
try {
await mongoose.connect(config.database.uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
logger.info('Connected to database');
} catch (error) {
logger.error('Database connection failed:', error);
throw error;
}
}
Adding Custom Middleware
Create middleware files in the src/middleware
directory:
// src/middleware/rate-limit.js
import rateLimit from 'express-rate-limit';
export const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
});
Apply middleware in your server configuration:
// src/index.js
import express from 'express';
import { MCPServer } from '@trmx/server';
import { apiLimiter } from './middleware/rate-limit';
import { authenticate } from './middleware/auth';
// Create Express app
const app = express();
// Apply middleware
app.use(apiLimiter);
app.use(authenticate);
// Create MCP server with custom app
const server = new MCPServer({
app,
// Other configuration...
});
Common Customizations
Custom Error Handling
Enhance error handling with a custom error handler:
// src/utils/error-handler.js
import { logger } from '../config/logger';
export class AppError extends Error {
constructor(message, statusCode = 500, details = {}) {
super(message);
this.statusCode = statusCode;
this.details = details;
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
export function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
// Log the error
logger.error(`Error [${statusCode}]: ${message}`, {
path: req.path,
method: req.method,
error: err.stack,
details: err.details,
});
// Send response to client
res.status(statusCode).json({
error: {
message,
...(process.env.NODE_ENV === 'development' ? { stack: err.stack } : {}),
...(err.details ? { details: err.details } : {}),
},
});
}
Custom Logging
Enhance logging for better debugging:
// src/config/logger.js
import winston from 'winston';
import { config } from './index';
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// Create logger
export const logger = winston.createLogger({
level: config.logLevel,
format: logFormat,
defaultMeta: { service: 'mcp-server' },
transports: [
// Console transport
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
return `${timestamp} ${level}: ${message} ${
Object.keys(meta).length ? JSON.stringify(meta) : ''
}`;
})
),
}),
// Add file transport in production
...(config.environment === 'production'
? [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' }),
]
: []),
],
});
Next Steps
- Create a Custom Tool to add functionality to your MCP server
- Add Authentication to secure your MCP server
- Deploy Your MCP Server to make it available online
- Check out the Python Template if you prefer Python