Skip to main content

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