Skip to main content

Securing Your MCP Server

This guide explains best practices for securing your MCP server and protecting sensitive data.

Introduction

Security is a critical aspect of deploying MCP servers, especially when handling sensitive user data or integrating with powerful AI models. This guide covers essential security measures to protect your MCP applications from common threats.

Authentication Methods

MCP supports several authentication methods to secure your server:

API Key Authentication

The simplest authentication method is API key-based authentication:

// src/config/auth.js
export const authConfig = {
apiKey: {
enabled: true,
// In production, use environment variables
keys: [process.env.MCP_API_KEY],
// Optional: key rotation settings
rotation: {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
automaticRotation: false,
},
},
};

Clients then authenticate using the API key:

const client = new MCPClient({
serverUrl: 'https://your-mcp-server.trmx.ai',
apiKey: 'your-api-key',
});

OAuth Authentication

For more robust authentication, implement OAuth as described in the OAuth Guide.

JWT Authentication

JSON Web Tokens provide a stateless authentication mechanism:

// src/middleware/jwt-auth.js
import jwt from 'jsonwebtoken';

export function jwtAuth(req, res, next) {
const authHeader = req.headers.authorization;

if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}

const token = authHeader.split(' ')[1];

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}

HTTPS and TLS Configuration

Always use HTTPS in production to encrypt data in transit.

Automatic HTTPS with TRMX AI

If you're deploying to TRMX AI's platform, HTTPS is configured automatically with proper TLS certificates.

Manual HTTPS Configuration

For self-hosted deployments, configure HTTPS:

// src/index.js
import https from 'https';
import fs from 'fs';
import { MCPServer } from '@trmx/server';

async function main() {
const httpsOptions = {
key: fs.readFileSync('path/to/private-key.pem'),
cert: fs.readFileSync('path/to/certificate.pem'),
// Optional: configure minimum TLS version
minVersion: 'TLSv1.2',
};

const server = new MCPServer({
httpsOptions,
port: 443,
// Other configuration...
});

await server.start();
}

main().catch(console.error);

Access Control

Role-Based Access Control (RBAC)

Implement RBAC to restrict access to certain operations:

// src/middleware/rbac.js
export function rbacMiddleware(requiredRole) {
return (req, res, next) => {
// Ensure user is authenticated
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}

// Check user role
if (!req.user.roles || !req.user.roles.includes(requiredRole)) {
return res.status(403).json({ error: 'Forbidden: Insufficient permissions' });
}

next();
};
}

Usage in routes:

// Apply RBAC to specific routes
app.get('/admin/tools', rbacMiddleware('admin'), (req, res) => {
// Admin-only endpoint
});

app.get('/api/tools/dangerous-tool', rbacMiddleware('power-user'), (req, res) => {
// Power user endpoint
});

Tool and Resource Permissions

Configure access controls for specific tools and resources:

// src/tools/sensitive-tool.js
export const sensitiveTool = {
name: 'sensitive_data_tool',
description: 'Accesses sensitive data',
permissions: ['admin', 'data-analyst'], // Only these roles can use this tool
// Tool implementation...
};

Rate Limiting

Implement rate limiting to prevent abuse:

// 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, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
message: 'Too many requests, please try again later.',
});

export const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 login attempts per hour
standardHeaders: true,
legacyHeaders: false,
message: 'Too many login attempts, please try again later.',
});

Apply middleware:

// Apply to all requests
app.use(apiLimiter);

// Apply to authentication routes
app.use('/auth/login', authLimiter);

Input Validation

Always validate input data:

// src/middleware/validation.js
import { body, validationResult } from 'express-validator';

export function validateToolCall() {
return [
body('name').isString().notEmpty().trim(),
body('arguments').isObject(),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
];
}

Apply validation:

app.post('/api/tools/call', validateToolCall(), toolController.callTool);

Handling Sensitive Data

Environment Variables

Store sensitive data in environment variables, not in code:

// .env
MCP_API_KEY=your-secret-api-key
OPENAI_API_KEY=your-openai-key
DATABASE_URL=your-database-connection-string
JWT_SECRET=your-jwt-secret

// src/config/index.js
import dotenv from 'dotenv';

dotenv.config();

export const config = {
apiKey: process.env.MCP_API_KEY,
openaiKey: process.env.OPENAI_API_KEY,
databaseUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
};

Data Encryption

Encrypt sensitive data before storing:

// src/utils/encryption.js
import crypto from 'crypto';

const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // Must be 32 bytes for aes-256-gcm
const IV_LENGTH = 16; // For AES, this is always 16

export function encrypt(text) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(ENCRYPTION_KEY), iv);

let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');

const authTag = cipher.getAuthTag().toString('hex');
return `${iv.toString('hex')}:${encrypted}:${authTag}`;
}

export function decrypt(text) {
const [ivHex, encryptedHex, authTagHex] = text.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(ENCRYPTION_KEY), iv);

decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
decrypted += decipher.final('utf8');

return decrypted;
}

Content Security Policy (CSP)

Implement CSP headers to prevent XSS attacks:

// src/middleware/security-headers.js
import helmet from 'helmet';

export function securityHeaders() {
return helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // Adjust as needed
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:'],
connectSrc: ["'self'", 'https://api.openai.com'], // Add any external APIs
},
},
});
}

CORS Configuration

Configure CORS to restrict which domains can access your API:

// src/middleware/cors.js
import cors from 'cors';

export function configureCors() {
return cors({
origin: process.env.NODE_ENV === 'production'
? ['https://your-app.com', 'https://admin.your-app.com']
: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400, // 24 hours
});
}

Logging and Monitoring

Implement secure logging practices:

// src/utils/logger.js
import winston from 'winston';

export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
// Sanitize sensitive data
winston.format((info) => {
if (info.apiKey) info.apiKey = '[REDACTED]';
if (info.password) info.password = '[REDACTED]';
return info;
})()
),
transports: [
new winston.transports.Console(),
// Add file transport in production
...(process.env.NODE_ENV === 'production'
? [new winston.transports.File({ filename: 'logs/error.log', level: 'error' })]
: []),
],
});

Security Scanning and Auditing

Dependency Scanning

Regularly audit your dependencies:

# Using npm
npm audit

# Using yarn
yarn audit

# Using snyk
snyk test

Code Scanning

Use static analysis tools:

# ESLint security rules
npm install eslint-plugin-security

Configure ESLint:

// .eslintrc.js
module.exports = {
plugins: ['security'],
extends: ['plugin:security/recommended'],
};

Deployment Security

Docker Security

If deploying with Docker, secure your containers:

# Dockerfile
FROM node:18-alpine

# Use non-root user
RUN addgroup -g 1001 -S mcp && \
adduser -u 1001 -S mcp -G mcp

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

# Set ownership
RUN chown -R mcp:mcp /app

# Switch to non-root user
USER mcp

# Use environment variables for sensitive data
ENV NODE_ENV production

# Expose only necessary port
EXPOSE 3000

CMD ["node", "dist/index.js"]

Emergency Response Plan

Develop a plan for security incidents:

  1. Containment: Steps to quickly limit damage
  2. Eradication: Removing the threat
  3. Recovery: Restoring systems securely
  4. Post-incident analysis: Learning from the incident

Document the plan and ensure team members know their roles.

Security Checklist

Security Measures

Implement these essential security practices for your MCP server:

  • Implement HTTPS with proper TLS configuration
  • Configure authentication (API keys, OAuth, or JWT)
  • Implement rate limiting
  • Validate all inputs
  • Configure CORS properly
  • Set up security headers
  • Encrypt sensitive data
  • Implement secure logging
  • Regularly audit dependencies
  • Document and test emergency response plan

Next Steps