MCP Security Framework
by aself101
README.md
# MCP Security Framework
[](https://www.npmjs.com/package/mcp-secure-server)
[](LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org/)
[](test/)
[](test/)
A secure-by-default MCP server built on the official SDK with 5-layer validation. Provides defense-in-depth against traditional attacks and AI-driven threats.
This framework implements defense-in-depth security with zero configuration required, protecting MCP servers from path traversal, command injection, SQL injection, XSS, prototype pollution, SSRF, and 20+ additional attack vectors.
## Quick Start
### Installation
```bash
npm install mcp-secure-server
```
### Basic Usage
```typescript
import { SecureMcpServer } from 'mcp-secure-server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// Create secure server with a security preset
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{
securityLevel: 'standard', // 'basic' | 'standard' | 'paranoid' | 'custom'
// Layer 4 denies any tools/call whose name is not registered here (fail
// closed). server.tool() below does NOT register with Layer 4 by itself.
toolRegistry: [{ name: 'calculator', sideEffects: 'none' }]
}
);
// Register tools exactly like McpServer
server.tool('calculator', 'Basic calculator', {
expression: z.string()
}, async ({ expression }) => {
// Security framework automatically blocks malicious inputs
// NOTE: eval() used for demo only - use a safe math parser in production
return { content: [{ type: 'text', text: `Result: ${eval(expression)}` }] };
});
// Connect - transport is automatically wrapped with security
const transport = new StdioServerTransport();
await server.connect(transport);
```
### Security Presets
Choose your security level with a single option:
```typescript
// Development: relaxed limits, minimal validation
const devServer = new SecureMcpServer(
{ name: 'dev', version: '1.0.0' },
{ securityLevel: 'basic' }
);
// Production: balanced security (default)
const prodServer = new SecureMcpServer(
{ name: 'prod', version: '1.0.0' },
{ securityLevel: 'standard' }
);
// High-security: maximum protection
const secureServer = new SecureMcpServer(
{ name: 'secure', version: '1.0.0' },
{ securityLevel: 'paranoid' }
);
// Custom: override specific values within a preset
const customServer = new SecureMcpServer(
{ name: 'custom', version: '1.0.0' },
{
securityLevel: 'standard',
maxRequestsPerMinute: 60 // Override just this value
}
);
```
| Preset | Use Case | Message Size | Rate Limit | Burst | Automation Detection |
|--------|----------|--------------|------------|-------|---------------------|
| `basic` | Development, testing | 100KB | 120/min | 30/10s | Disabled |
| `standard` | Production (default) | 50KB | 30/min | 10/10s | Enabled |
| `paranoid` | High-risk, compliance | 25KB | 15/min | 5/5s | Enabled (strict) |
| `custom` | Full control | You decide | You decide | You decide | You decide |
### Programmatic Preset Access
Access preset configurations programmatically for dynamic configuration, validation, or custom tooling:
```typescript
import {
SECURITY_PRESETS,
resolvePreset,
getDefaultPreset,
isValidPreset
} from 'mcp-secure-server';
// Get the default preset name
const defaultName = getDefaultPreset(); // 'standard'
// Validate user input
const userInput = 'paranoid';
if (isValidPreset(userInput)) {
const config = resolvePreset(userInput);
console.log(config.maxMessageSize); // 25600
console.log(config.maxRequestsPerMinute); // 15
}
// Iterate all presets for documentation or UI
for (const [name, config] of Object.entries(SECURITY_PRESETS)) {
console.log(`${name}: ${config.maxRequestsPerMinute} req/min`);
}
// Build dynamic configuration
function createServer(env: string) {
const level = env === 'production' ? 'paranoid' : 'basic';
return new SecureMcpServer(
{ name: 'dynamic', version: '1.0.0' },
{ securityLevel: level }
);
}
```
### With Logging (Opt-in)
```typescript
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{
securityLevel: 'standard',
enableLogging: true,
verboseLogging: true,
logPerformanceMetrics: true,
logLevel: 'debug'
}
);
```
Full TypeScript support with exported types for all parameters, configurations, and responses.
## Table of Contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Security Layers](#security-layers)
- [Installation](#installation)
- [TypeScript Support](#typescript-support)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Tool Policies Configuration](#tool-policies-configuration)
- [API Reference](#api-reference)
- [HTTP Transport](#http-transport)
- [Layer 5 Customization](#layer-5-customization)
- [Security Features](#security-features)
- [Attack Coverage](#attack-coverage)
- [Error Handling](#error-handling)
- [Claude Desktop Integration](#claude-desktop-integration)
- [Development](#development)
- [Troubleshooting](#troubleshooting)
- [Cookbook Examples](#cookbook-examples)
## Cookbook Examples
Example MCP servers demonstrating the security framework. Each server includes input validation and attack prevention.
| Server | Description | Tools | Auth |
|--------|-------------|-------|------|
| [advanced-validation-server](cookbook/advanced-validation-server) | Layer 5 custom validators (PII detection, geofencing, business hours, egress tracking) | Financial query, batch process, export data, API call | None |
| [api-wrapper-server](cookbook/api-wrapper-server) | Safe REST API wrapping with domain restrictions and rate limiting | Weather, currency conversion, news headlines | None |
| [cli-wrapper-server](cookbook/cli-wrapper-server) | Safe CLI tool wrapping with command injection prevention | Git status, image resize, PDF metadata, video encode | None |
| [database-server](cookbook/database-server) | Secure database operations with SQL injection prevention | User queries, order creation, report generation | None |
| [filesystem-server](cookbook/filesystem-server) | Protected file system access with path traversal prevention | Read files, list directories, search files | None |
| [http-server](cookbook/http-server) | Simple HTTP transport with `createHttpServer()` | Calculator, echo | None |
| [image-gen-server](cookbook/image-gen-server) | Unified image generation across 5 providers (BFL, Google, Ideogram, OpenAI, Stability) | Generate, edit, upscale, describe images | API keys |
| [kenpom-server](cookbook/kenpom-server) | College basketball analytics and efficiency ratings | Ratings, schedules, scouting reports, player stats | KenPom login |
| [monitoring-server](cookbook/monitoring-server) | Observability with metrics, audit logging, and alerts | Security metrics, audit log, alerts, Prometheus export | None |
| [multi-endpoint-server](cookbook/multi-endpoint-server) | Multiple HTTP endpoints with `createSecureHttpHandler()` | Admin (list-users, system-stats), Public (health, status) | None |
| [nba-server](cookbook/nba-server) | NBA stats, live scores, and player data | Player stats, box scores, live scoreboard | None |
| [transaction-server](cookbook/transaction-server) | Method chaining enforcement for secure transaction workflows | Session, accounts, prepare/confirm/execute transactions | None |
See the [cookbook README](cookbook/README.md) for setup instructions and detailed documentation.
## Overview
The MCP Security Framework acts as a universal wrapper for any MCP server, providing comprehensive security validation through a multi-layered architecture. It implements:
- **5-Layer Defense Pipeline** - Structure, Content, Behavior, Semantics, and Contextual validation
- **Zero Configuration** - Security enabled by default with sensible defaults
- **Universal Compatibility** - Works with any MCP server using @modelcontextprotocol/sdk
- **Extensible Layer 5** - Add custom validators, domain restrictions, OAuth validation
- **Tested** - 1250 tests with 93% line coverage, including a `dist/` smoke suite that runs the published artifact in a separate process
- **Opt-in File Logging** - the audit logger is off by default; blocked requests always emit one `[SECURITY]` line to **stderr** (never stdout)
- **Performance Optimized** - Content caching and efficient pattern detection
- **Full TypeScript Support** - Complete type definitions with strict mode
## Architecture
```
Request → Layer 1 → Layer 2 → Layer 3 → Layer 4 → Layer 5 → MCP Server
│ │ │ │ │
Structure Content Behavior Semantics Contextual
Validation Validation Validation Validation Validation
```
### Visual Overview
```
MCP Security Framework (5 Layers by Default)
│
┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
│ │ │ │ │ │
┌───▼────┐ ┌─────▼─────┐ ┌────▼────┐ ┌────▼─────┐ ┌─────▼──────┐
│ Layer 1│ │ Layer 2 │ │ Layer 3 │ │ Layer 4 │ │ Layer 5 │
│ Struct.│ │ Content │ │ Behavior│ │ Semantics│ │ Contextual │
└────────┘ └───────────┘ └─────────┘ └──────────┘ └────────────┘
│JSON-RPC│ │Injection │ │Rate │ │Tool │ │Custom │
│Format │ │Detection │ │Limiting │ │Contracts │ │Validators │
│Size │ │XSS/SQLi │ │Burst │ │Quotas │ │Domain/OAuth│
│Encoding│ │Path Trav. │ │Patterns │ │Policies │ │Response Val│
└────────┘ └───────────┘ └─────────┘ └──────────┘ └────────────┘
```
## Security Layers
### Layer 1 - Structure Validation
Validates the fundamental structure of incoming JSON-RPC messages.
**Protections:**
- JSON-RPC 2.0 format validation
- Request size limits (default: 50KB)
- Message encoding validation
- Parameter count limits
- Per-string parameter length limits (default: 5,000 chars)
- Method name length limits
**Configuration:**
```typescript
{
maxMessageSize: 50000, // Maximum message size in bytes
maxParamCount: 100, // Maximum recursive parameter count (set to Infinity to disable)
maxStringLength: 5000 // Maximum length of any single string parameter value (chars)
}
```
Method name length is capped at 100 characters. This limit is not configurable — MCP
method names are protocol-level identifiers (`tools/call`, `resources/read`, …) and
never legitimately approach it.
> **Note:** `maxMessageSize` must leave headroom above `maxStringLength` — the message
> envelope is larger than the string it carries, and the message-size check fires first.
### Layer 2 - Content Validation
Detects and blocks malicious content patterns in request parameters.
**Protections:** Path traversal, command injection, SQL/NoSQL injection, XSS, prototype pollution, XML entity attacks (XXE), CRLF injection, SSRF, CSV injection, LOLBins, GraphQL introspection, deserialization attacks, JNDI/Log4Shell, buffer overflow patterns, and more.
See [SECURITY.md](https://github.com/aself101/mcp-secure-server/blob/main/SECURITY.md#attack-vectors) for the complete list of 200+ attack patterns with examples.
**Configuration:**
```typescript
{
maxParamBytes: 50000, // Max serialized params payload (bytes)
contentValidation: {
enabled: true,
debugMode: false // Enable for detailed pattern match info
}
}
```
> **Note — the size caps stack.** A large payload faces four independent ceilings, and the
> first one to fire names itself in the rejection: `maxMessageSize` (Layer 1 envelope),
> `maxStringLength` (Layer 1, per string), `maxParamBytes` (Layer 2, serialized params),
> and `suspiciousMessageSize` (Layer 3, hard block). Tools that legitimately accept large
> structured payloads (bulk saves, document stores) need all four raised together — raising
> only one moves the rejection to the next ceiling in the stack. The per-tool `maxArgsSize`
> (Layer 4) is a fifth, tool-scoped ceiling that cannot admit what the global caps reject.
> **Note — shell-access patterns require invocation context.** The `command.shellAccess`
> patterns run at every security level (ALWAYS_CHECK), including STORAGE tools with
> `relaxedFields`. As of 0.0.19-security they match shell *invocations*
> (`powershell -enc …`, `cmd /c …`, non-shebang `/bin/sh`), not bare mentions — stored
> prose like "PowerShell users" or a `#!/bin/sh` shebang no longer trips them. Known
> limitation: stored content quoting a *complete* shell invocation (e.g. a security
> report's reverse-shell example) is still rejected at STORAGE level. This is deliberate;
> if it becomes a problem for your workload, the escape hatch is field-level exclusion via
> `relaxedFields`, whose content is not pattern-scanned at all.
### Layer 3 - Behavior Validation
Rate limiting and request pattern analysis to prevent abuse.
**Protections:**
- Requests per minute rate limiting
- Requests per hour rate limiting
- Burst detection (configurable time window)
- Automation detection via timing analysis
- Large message flagging
**Configuration:**
```typescript
{
maxRequestsPerMinute: 30, // Rate limit per minute
maxRequestsPerHour: 500, // Rate limit per hour
burstThreshold: 10, // Max requests in burst window
burstWindowMs: 10000, // Burst detection window in ms (default: 10s)
suspiciousMessageSize: 20000, // BLOCK messages larger than this (bytes) — a hard
// rejection, not a log-only flag; keep aligned with
// maxMessageSize/maxParamBytes for large-payload tools
automationDetection: {
enabled: true, // Enable timing-based automation detection
sampleSize: 5, // Number of requests to analyze
maxVariance: 50, // Max timing variance (ms) before flagging
minInterval: 100, // Min avg interval (ms) to flag as automation
maxInterval: 2000 // Max avg interval (ms) to flag as automation
}
}
```
**Automation Detection:** Analyzes request timing patterns to detect automated scripts. When enabled, it monitors the variance in request intervals - suspiciously consistent timing (low variance) indicates automation rather than human interaction.
### Layer 4 - Semantic Validation
Tool contract enforcement and resource access policies.
**Protections:**
- Tool argument validation against schemas
- Response size limits (egress control)
- Per-tool quota enforcement
- Side effect declarations
- Filesystem access control via resource policies
- Session management
- Method chaining enforcement (opt-in)
**Configuration:**
```typescript
{
toolRegistry: [
{
name: 'my-database-tool',
sideEffects: 'write', // 'none' | 'read' | 'write' | 'network'
maxArgsSize: 5000, // Max argument size in bytes
maxEgressBytes: 100000, // Max response size
quotaPerMinute: 30,
quotaPerHour: 500,
argsShape: { // Expected argument schema
query: { type: 'string' },
limit: { type: 'number' }
}
}
],
resourcePolicy: {
allowedSchemes: ['file'],
rootDirs: ['./data', './public'],
denyGlobs: ['/etc/**', '**/*.key', '**/.env'],
maxPathLength: 4096,
maxReadBytes: 2000000 // 2MB max file read
},
maxSessions: 5000,
sessionTtlMs: 1800000 // 30 minutes
}
```
#### Method Chaining Enforcement
Layer 4 can enforce valid method call sequences to prevent abuse patterns like calling dangerous tools without proper initialization.
**Enable chaining enforcement:**
```typescript
{
enforceChaining: true, // Enable method chaining (default: false)
chainingDefaultAction: 'deny', // 'allow' | 'deny' when no rule matches
chainingRules: [
// Allow any method to call initialize
{ from: '*', to: 'initialize' },
// After initialize, can list tools or resources
{ from: 'initialize', to: 'tools/list' },
{ from: 'initialize', to: 'resources/list' },
// After listing tools, can call them
{ from: 'tools/list', to: 'tools/call' },
// Tool-to-tool calls allowed
{ from: 'tools/call', to: 'tools/call' },
]
}
```
**ChainingRule type:**
```typescript
interface ChainingRule {
from: string; // Method to transition from ('*' for any)
to: string; // Method to transition to ('*' for any)
fromTool?: string; // Tool name glob pattern (e.g., 'file-*', '*-http*')
toTool?: string; // Tool name glob pattern
fromSideEffect?: SideEffects; // 'none' | 'read' | 'write' | 'network'
toSideEffect?: SideEffects;
action?: 'allow' | 'deny'; // Default: 'allow'
id?: string; // Rule identifier for logging
description?: string; // Human-readable description
}
```
**Advanced example - block dangerous transitions:**
```typescript
{
enforceChaining: true,
chainingDefaultAction: 'allow', // Allow by default
chainingRules: [
// Block read tools from calling write tools directly
{
from: 'tools/call',
to: 'tools/call',
fromSideEffect: 'read',
toSideEffect: 'write',
action: 'deny',
id: 'no-read-to-write'
},
// Block file-* tools from calling *-http* tools
{
from: 'tools/call',
to: 'tools/call',
fromTool: 'file-*',
toTool: '*-http*',
action: 'deny',
id: 'no-file-to-http'
}
]
}
```
Rules are evaluated first-match-wins. Tool patterns use simple glob matching (`*` = any chars, `?` = single char).
### Layer 5 - Contextual Validation
Custom validators, domain restrictions, and response filtering. Fully extensible.
**Protections:**
- Custom validator registration with priorities
- Domain blocklist/allowlist enforcement
- OAuth URL validation
- Response content validation (PII detection, etc.)
- Cross-request state via context store
- Global rules that run before validators
**Configuration:**
```typescript
{
contextual: {
enabled: true, // Set false to disable Layer 5
domainRestrictions: {
enabled: true,
blockedDomains: ['evil.com', 'malicious.net'],
allowedDomains: [] // Empty = allow all except blocked
},
oauthValidation: {
enabled: true,
allowedDomains: ['oauth.example.com'],
blockDangerousSchemes: true
},
rateLimiting: {
enabled: true,
limit: 20,
windowMs: 60000
}
}
}
```
## Installation
### From npm
```bash
# Install in your project
npm install mcp-secure-server
# Or install globally
npm install -g mcp-secure-server
```
### From Source
```bash
# Clone the repository
git clone https://github.com/aself101/mcp-secure-server.git
cd mcp-secure-server
# Install dependencies
npm install
# Build TypeScript
npm run build
```
**Dependencies** (regular, installed with the package — not peer dependencies):
- `@modelcontextprotocol/sdk` - MCP SDK (the server you wrap is built on the copy installed here)
- `zod` - Schema validation
- `minimatch` - Glob matching for tool-policy names
- `winston` - The optional audit logger's transports
## TypeScript Support
This package is written in TypeScript with strict mode enabled (`noUncheckedIndexedAccess`, `strictNullChecks`). All exports include complete type definitions.
### Exported Types
```typescript
import {
// Main classes
SecureMcpServer,
SecureTransport,
ContextualValidationLayer,
ContextualConfigBuilder,
// Factory functions
createContextualLayer,
// Type guards
isSeverity,
isViolationType,
isError,
getErrorMessage,
// Types
SecurityOptions,
ValidationResult,
Severity,
ViolationType,
ToolSpec,
ResourcePolicy,
ValidationContext
} from 'mcp-secure-server';
```
### Type-Safe Configuration
```typescript
import { SecureMcpServer } from 'mcp-secure-server';
import type { SecureMcpServerOptions } from 'mcp-secure-server';
const options: SecureMcpServerOptions = {
securityLevel: 'standard',
maxMessageSize: 50000,
maxParamCount: 100, // Recursive key count limit (Infinity to disable)
maxStringLength: 5000, // Per-string parameter length limit (chars)
maxParamBytes: 50000, // Serialized params payload limit (bytes, Layer 2)
maxRequestsPerMinute: 30,
enableLogging: true,
contextual: {
enabled: true,
domainRestrictions: {
enabled: true,
blockedDomains: ['evil.com']
}
}
};
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
options // TypeScript validates all options
);
```
> `SecureMcpServerOptions` is the constructor's actual parameter type and always carries
> the full option set. The older `SecurityOptions` name is now a deprecated alias of it —
> it was previously a separate interface that drifted out of sync with the real options.
### Validation Results
```typescript
interface ValidationResult {
passed: boolean;
allowed: boolean; // alias of passed (backward compatibility)
severity: Severity; // 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
reason: string | null; // sanitized failure reason
violationType: ViolationType | string | null; // 'PATH_TRAVERSAL' | 'SQL_INJECTION' | ...
confidence: number; // 0.0 - 1.0
timestamp: number;
layerName: string | null;
detectionLayer?: string;
validatorSource?: string;
validationTime?: number;
}
```
All non-suffixed fields are **required** on the interface. In plain JavaScript, custom
Layer 5 validators may return partial literals like `{ passed: true }` — the framework
normalizes them at runtime. In TypeScript, annotate custom validators loosely (or return
the full shape); a literal `{ passed: true }` does not satisfy `ValidationResult`.
### Building from Source
```bash
# Install dependencies
npm install
# Build TypeScript to JavaScript
npm run build
# Output is in dist/
ls dist/
# index.js, index.d.ts, security/*.js, security/*.d.ts, types/*.d.ts
```
## Configuration
### Full Configuration Reference
```typescript
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{
// ═══════════════════════════════════════════
// Layer 1 & 2 - Structure & Content Validation
// ═══════════════════════════════════════════
maxMessageSize: 50000, // Max message size (bytes)
maxParamCount: 100, // Max recursive parameters (Infinity to disable)
maxStringLength: 5000, // Max length of any single string parameter (chars)
maxParamBytes: 50000, // Max serialized params payload (bytes, Layer 2)
// (method name length is fixed at 100 chars — not configurable)
// ═══════════════════════════════════════════
// Layer 2 - Content Validation
// ═══════════════════════════════════════════
// Enabled by default with all pattern detection
// ═══════════════════════════════════════════
// Layer 3 - Behavior Validation
// ═══════════════════════════════════════════
maxRequestsPerMinute: 30, // Rate limit per minute
maxRequestsPerHour: 500, // Rate limit per hour
burstThreshold: 10, // Max requests in burst window
burstWindowMs: 10000, // Burst window duration (ms)
suspiciousMessageSize: 20000, // Block large messages (bytes) — hard rejection
automationDetection: { // Timing-based automation detection
enabled: true, // Enable/disable detection
sampleSize: 5, // Requests to analyze
maxVariance: 50, // Max timing variance (ms)
minInterval: 100, // Min interval to flag (ms)
maxInterval: 2000 // Max interval to flag (ms)
},
// ═══════════════════════════════════════════
// Layer 4 - Semantic Validation
// ═══════════════════════════════════════════
toolRegistry: [ // Tool constraints
{
name: 'my-tool',
sideEffects: 'write',
maxArgsSize: 5000,
maxEgressBytes: 100000,
quotaPerMinute: 30
}
],
resourcePolicy: { // Filesystem access control
allowedSchemes: ['file'],
rootDirs: ['./data'],
denyGlobs: ['/etc/**', '**/*.key'],
maxReadBytes: 2000000
},
maxSessions: 5000,
sessionTtlMs: 1800000,
enforceChaining: false, // Enable method chaining (default: false)
chainingDefaultAction: 'deny', // 'allow' | 'deny' when no rule matches
chainingRules: [ // Method transition rules
{ from: '*', to: 'initialize' },
{ from: 'initialize', to: 'tools/list' },
{ from: 'tools/list', to: 'tools/call' },
{ from: 'tools/call', to: 'tools/call' },
// Advanced: tool patterns and side effects
// { from: 'tools/call', to: 'tools/call', fromTool: 'read-*', toTool: 'write-*', action: 'deny' }
],
// ═══════════════════════════════════════════
// Layer 5 - Contextual Validation
// ═══════════════════════════════════════════
contextual: {
enabled: true, // false to disable Layer 5
domainRestrictions: {
enabled: true,
blockedDomains: ['evil.com'],
allowedDomains: [] // Empty = allow all except blocked
},
oauthValidation: {
enabled: true,
allowedDomains: ['oauth.example.com'],
blockDangerousSchemes: true
},
rateLimiting: {
enabled: true,
limit: 20,
windowMs: 60000
}
},
// ═══════════════════════════════════════════
// Error responses
// ═══════════════════════════════════════════
enableDetailedErrors: false, // Include redacted violation reason in top-level
// error.message (detail is always in data.reason)
// ═══════════════════════════════════════════
// Logging (file/audit logger disabled by default; blocked requests still
// emit one [SECURITY] line to stderr — see ErrorSanitizerOptions.enableSecurityLogging)
// ═══════════════════════════════════════════
enableLogging: false, // Enable security logging
verboseLogging: false, // Detailed decision logs
logPerformanceMetrics: false, // Timing statistics
logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error'
logDir: './logs' // Log directory; also settable via LOG_DIR env
}
);
```
### Log Directory Resolution
The log directory is resolved in this order:
1. `logDir` option passed to the constructor
2. `LOG_DIR` environment variable
3. `<cwd>/logs` (default)
A relative value is resolved against the current working directory. Set `logDir`
explicitly when the process may run with a non-writable working directory — for
example, MCP hosts (such as Claude Desktop) that launch servers with `cwd="/"`,
where the default would resolve to the unwritable `/logs`.
If the resolved directory cannot be created or written, the logger **degrades to
no-op logging** rather than crashing the host (logging must never crash the
application). When this happens it emits a one-time warning to **stderr**, and
`getSecurityStats().logger.fileLoggingAvailable` reports `false` (with
`writeErrors`/`lastWriteError` for late failures) so the degraded state is
observable rather than silent.
## Tool Policies Configuration
Tool policies allow you to define security levels for individual MCP tools, enabling context-aware content validation. Tools that store documentation can have relaxed pattern detection, while tools that execute commands use full validation.
### Security Levels
| Level | Description | Use Case |
|-------|-------------|----------|
| `EXECUTION` | Full validation - all attack patterns checked | Command execution, file writes, system operations |
| `QUERY` | Standard validation - SQL/NoSQL patterns added | Database queries, API calls, search operations |
| `STORAGE` | Relaxed validation - critical patterns only | Issue trackers, notes, documentation storage |
| `DISPLAY` | Minimal validation - XSS and deserialization only | Read-only queries, help output, status displays |
### Configuration File
Create a `tool-policies.json` file in your project root or specify a path via the `TOOL_POLICIES_PATH` environment variable.
**File resolution order:**
1. `TOOL_POLICIES_PATH` environment variable
2. `./tool-policies.json` (current working directory)
3. `~/.config/mcp-secure-server/tool-policies.json` (user config)
### Basic Example
```json
{
"version": "2.0",
"tools": {
"save_note": {
"level": "STORAGE",
"relaxedFields": ["content", "title"],
"description": "Stores user notes"
},
"get_status": {
"level": "DISPLAY",
"description": "Read-only status query"
},
"execute_command": {
"level": "EXECUTION",
"description": "Runs shell commands"
}
},
"defaultLevel": "EXECUTION"
}
```
### Full Schema
```json
{
"version": "2.0",
"basePolicies": {
"storage-content": {
"level": "STORAGE",
"relaxedFields": ["content", "description", "title"],
"description": "Base policy for content storage tools"
},
"display-readonly": {
"level": "DISPLAY",
"description": "Base policy for read-only tools"
}
},
"patterns": [
{ "match": "get_*", "policy": "display-readonly" },
{ "match": "list_*", "policy": "display-readonly" },
{ "match": "search_*", "policy": "display-readonly" },
{ "match": "{save,create,update}_*", "policy": "storage-content" }
],
"tools": {
"execute_sql": {
"level": "QUERY",
"description": "Database queries with SQL injection checks"
},
"save_document": "storage-content",
"dangerous_operation": {
"level": "EXECUTION",
"description": "High-risk operation requiring full validation"
}
},
"defaultLevel": "EXECUTION"
}
```
### Schema Reference
| Field | Type | Description |
|-------|------|-------------|
| `version` | `"2.0"` | Required. Schema version (must be `"2.0"`) |
| `basePolicies` | `object` | Optional. Reusable policy definitions |
| `patterns` | `array` | Optional. Glob patterns for tool matching |
| `tools` | `object` | Optional. Explicit tool policy definitions |
| `defaultLevel` | `string` | Optional. Fallback level for unknown tools |
### Policy Object
```typescript
{
level: 'EXECUTION' | 'QUERY' | 'STORAGE' | 'DISPLAY', // Security level
relaxedFields?: string[], // Fields with relaxed validation
skipPatterns?: string[], // Pattern categories to skip
description?: string, // Documentation
extends?: string // Inherit from base policy
}
```
### Pattern Matching
Patterns use [minimatch](https://github.com/isaacs/minimatch) glob syntax:
| Pattern | Matches |
|---------|---------|
| `get_*` | `get_users`, `get_status`, `get_config` |
| `*_issues` | `query_issues`, `search_issues`, `list_issues` |
| `{get,list}_*` | `get_users`, `list_users`, `get_config`, `list_items` |
| `file-*` | `file-read`, `file-write`, `file-delete` |
| `v?_tool` | `v1_tool`, `v2_tool` |
**Resolution order:**
1. Explicit tool definitions (highest priority)
2. Pattern matching (first match wins)
3. `defaultLevel` from config
4. `EXECUTION` level (secure default)
### Inheritance with `extends`
Policies can inherit from base policies and add/override fields:
```json
{
"version": "2.0",
"basePolicies": {
"base-storage": {
"level": "STORAGE",
"relaxedFields": ["content"]
}
},
"tools": {
"save_note": {
"extends": "base-storage",
"relaxedFields": ["extra_field"],
"description": "Inherits STORAGE level, merges relaxedFields"
}
}
}
```
The result for `save_note`:
- `level`: `STORAGE` (inherited)
- `relaxedFields`: `["content", "extra_field"]` (merged and deduplicated)
### Relaxed Fields
The `relaxedFields` array specifies parameter names that should use `STORAGE`-level validation regardless of the tool's overall level. Useful for tools that have both sensitive and content parameters:
```json
{
"tools": {
"create_issue": {
"level": "QUERY",
"relaxedFields": ["description", "title"],
"description": "QUERY level for project/priority, STORAGE for text content"
}
}
}
```
### Runtime Registration
You can also register tool policies programmatically:
```typescript
import { registerToolPolicy } from 'mcp-secure-server';
registerToolPolicy('my_custom_tool', {
level: 'STORAGE',
relaxedFields: ['content', 'notes'],
description: 'Custom documentation tool'
});
```
### Loading Configuration Programmatically
```typescript
import { initializeToolPolicies, resetToolPolicies } from 'mcp-secure-server';
// Load from object
initializeToolPolicies({
version: '2.0',
patterns: [
{ match: 'get_*', policy: { level: 'DISPLAY' } }
],
defaultLevel: 'QUERY'
});
// Reset to defaults
resetToolPolicies();
```
### Validation Behavior by Level
| Level | Checks SQL/NoSQL | Checks Command Injection | Checks Path Traversal | Checks XSS |
|-------|-----------------|-------------------------|----------------------|------------|
| `EXECUTION` | ✅ All | ✅ All (6 sub-categories) | ✅ | ✅ All |
| `QUERY` | ✅ All | ⚠️ Critical only (shellAccess, executionWrappers) | ✅ | ✅ All |
| `STORAGE` | ❌ | ⚠️ Critical only (shellAccess, executionWrappers) | ❌ | ✅ Critical |
| `DISPLAY` | ❌ | ⚠️ Critical only (shellAccess, executionWrappers) | ❌ | ✅ Critical |
All levels check critical sub-categories (shell access, execution wrappers, XSS basic/advanced vectors, deserialization). EXECUTION_ONLY sub-categories (systemInfo, fileOperations, networkOperations, basicInjection) run only at EXECUTION level to avoid false positives on common words like "top", "curl", "grep" in documentation content.
### Advanced Tool Policy Helpers
Additional utilities for programmatic tool policy management:
```typescript
import {
getToolsByLevel,
isRelaxedField,
isValidSecurityLevel,
defaultToolPolicies
} from 'mcp-secure-server';
// Get all tools configured at a specific security level
const storageLevelTools = getToolsByLevel('STORAGE');
// Returns: ['save_note', 'create_document', ...]
// Check if a field has relaxed validation for a tool
if (isRelaxedField('save_note', 'content')) {
// 'content' field uses STORAGE-level validation
}
// Validate security level strings
if (isValidSecurityLevel(userInput)) {
// userInput is 'EXECUTION' | 'QUERY' | 'STORAGE' | 'DISPLAY'
}
// Access default policies (useful for extending)
const defaults = defaultToolPolicies;
```
**Use cases:**
- **Auditing:** List all tools at each security level
- **Debugging:** Check if a specific field is relaxed
- **Dynamic configuration:** Validate user-provided security levels
- **Testing:** Access defaults for baseline comparison
## API Reference
### SecureMcpServer
Drop-in replacement for McpServer with built-in 5-layer security.
```typescript
import { SecureMcpServer } from 'mcp-secure-server';
const server = new SecureMcpServer(serverInfo, options);
```
#### MCP SDK Passthrough Methods
SecureMcpServer delegates to the underlying McpServer for most operations. Some methods add security enhancements.
**Enhanced Methods** (security added):
| Method | Enhancement |
|--------|-------------|
| `tool()` | Response validation via Layer 5 |
| `registerTool()` | Response validation via Layer 5 |
| `connect()` | Wraps transport with SecureTransport |
**Pure Passthrough Methods** (direct delegation to McpServer):
```typescript
// Resource and prompt registration
server.resource(name, uri, handler);
server.prompt(name, description, handler);
// Connection management
await server.close();
server.isConnected();
// Notification methods (MCP SDK passthrough)
server.sendResourceListChanged();
server.sendToolListChanged();
server.sendPromptListChanged();
```
**Example with all registration types:**
```typescript
const server = new SecureMcpServer({ name: 'demo', version: '1.0.0' });
// Tool registration (response validation enabled)
server.tool('add', 'Add numbers', { a: z.number(), b: z.number() },
async ({ a, b }) => ({ content: [{ type: 'text', text: `${a + b}` }] })
);
// Resource registration (passthrough)
server.resource('config', 'config://app', async () => ({
contents: [{ uri: 'config://app', text: JSON.stringify(config) }]
}));
// Prompt registration (passthrough)
server.prompt('greeting', 'Generate greeting', async () => ({
messages: [{ role: 'user', content: { type: 'text', text: 'Hello!' } }]
}));
await server.connect(new StdioServerTransport());
```
#### Security Methods
```typescript
// Get security statistics
const stats = server.getSecurityStats();
// {
// server: { uptime, totalLayers, enabledLayers, loggingEnabled },
// behaviorLayer?: { ...request/burst counters },
// logger?: { ...logging health, e.g. fileLoggingAvailable } // only when logging enabled
// }
// Get detailed security report (requires enableLogging: true)
const report = server.getVerboseSecurityReport();
// Generate full report to file (requires enableLogging: true)
await server.generateSecurityReport();
// Graceful shutdown with final report
await server.shutdown();
```
#### Property Accessors
```typescript
server.mcpServer; // Access underlying McpServer
server.server; // Access underlying Server
server.validationPipeline; // Access validation pipeline
```
### SecureTransport
Low-level transport wrapper for custom implementations.
```typescript
import { SecureTransport } from 'mcp-secure-server';
const secureTransport = new SecureTransport(
transport, // Original transport
validator, // Validation function
{
errorSanitizer // Optional error sanitizer
}
);
```
### HTTP Transport
For remote MCP servers, use the built-in HTTP transport with security validation. Zero external dependencies - uses `node:http` directly.
```typescript
import { SecureMcpServer } from 'mcp-secure-server';
import { z } from 'zod';
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{ enableLogging: true }
);
server.tool('add', 'Add two numbers', {
a: z.number(),
b: z.number()
}, async ({ a, b }) => ({
content: [{ type: 'text', text: `${a + b}` }]
}));
// Create HTTP server with security validation
const httpServer = server.createHttpServer({ endpoint: '/mcp' });
httpServer.listen(3000, () => {
console.log('MCP server listening on http://localhost:3000/mcp');
});
```
**Configuration options:**
```typescript
interface HttpServerOptions {
endpoint?: string; // MCP endpoint path (default: '/mcp')
maxBodySize?: number; // Max body size in bytes (default: 51200 = 50KB)
requestTimeout?: number; // Body parse timeout in ms (default: 30000)
sessionlessRequestsPerMinute?: number; // Per-client-IP cap on GET/DELETE (default: 60)
sessionlessRequestsPerHour?: number; // Per-client-IP cap on GET/DELETE (default: 600)
}
```
**GET and DELETE:** these carry no JSON-RPC body, so the 5-layer message pipeline
does not run on them. They are gated instead by the per-IP error lockout (an IP
throttled for probing cannot open SSE streams or tear down sessions) and by the
`sessionlessRequests*` ceilings above. On every method, a present `Mcp-Session-Id`
must be visible ASCII of at most 256 bytes or the request is rejected with 400
before the SDK or the pipeline sees it.
**Session ID handling:**
| Source | Value | Used By |
|--------|-------|---------|
| `Mcp-Session-Id` header | Client-provided | Layer 4 method-chaining state; audit log correlation |
| Missing header | `'stateless'` | One shared chaining state for all header-less clients |
> **Layer 3 rate limits and Layer 4 per-tool quotas are process-global, not per session.**
> They do not read the session id; every client draws from the same `maxRequestsPerMinute`
> and per-tool budgets. *(Until 0.0.21 this table said the opposite.)* For per-client
> ceilings on the HTTP path see `sessionlessRequests*` above and the per-IP error lockout.
**Standalone function:**
```typescript
import { SecureMcpServer, createSecureHttpServer } from 'mcp-secure-server';
const server = new SecureMcpServer({ name: 'x', version: '1.0' });
const httpServer = createSecureHttpServer(server, { endpoint: '/api/mcp' });
httpServer.listen(8080);
```
**Multiple endpoints:**
For services exposing multiple MCP servers on different paths, use `createSecureHttpHandler` to compose your own routing:
```typescript
import { SecureMcpServer, createSecureHttpHandler } from 'mcp-secure-server';
import { createServer } from 'node:http';
// Create separate MCP servers with different tools/permissions
const adminServer = new SecureMcpServer({ name: 'admin', version: '1.0' });
const publicServer = new SecureMcpServer({ name: 'public', version: '1.0' });
// Register tools on each server
adminServer.tool('delete-user', ...);
publicServer.tool('get-status', ...);
// Create handlers (validates requests, forwards to MCP SDK transport)
const adminHandler = createSecureHttpHandler(adminServer);
const publicHandler = createSecureHttpHandler(publicServer);
// Compose with custom routing
const httpServer = createServer(async (req, res) => {
if (req.url?.startsWith('/api/admin')) return adminHandler(req, res);
if (req.url?.startsWith('/api/public')) return publicHandler(req, res);
res.writeHead(404).end(JSON.stringify({ error: 'Not found' }));
});
httpServer.listen(3000);
```
| Function | Purpose |
|----------|---------|
| `createSecureHttpServer` | Single endpoint, includes routing |
| `createSecureHttpsServer` | Same, with TLS (key/cert) — recommended for production |
| `createSecureHttpHandler` | Request handler only, you provide routing |
**CORS:** Add headers manually or wrap with a CORS middleware.
**HTTPS:** Use the built-in factory (or deploy behind a TLS-terminating reverse proxy):
```typescript
import { readFileSync } from 'node:fs';
import { createSecureHttpsServer } from 'mcp-secure-server';
const httpsServer = createSecureHttpsServer(server, {
key: readFileSync('server.key'), // TLS private key (PEM string or Buffer)
cert: readFileSync('server.cert'), // TLS certificate (PEM string or Buffer)
ca: undefined, // optional CA chain
endpoint: '/mcp'
});
httpsServer.listen(3443);
```
### Available Exports
```typescript
import {
SecureMcpServer, // Main secure server class
SecureTransport, // Transport wrapper
createSecureHttpServer, // HTTP server factory (single endpoint)
createSecureHttpHandler, // HTTP handler factory (multi-endpoint)
createSecureHttpsServer, // HTTPS server factory with TLS
ErrorRateLimiter, // Rate limiter for error responses
getClientIp, // Extract client IP from request
ContextualValidationLayer, // Layer 5 class
ContextualConfigBuilder, // Builder for Layer 5 config
createContextualLayer, // Factory for Layer 5
// Tool policy configuration
initializeToolPolicies, // Load config from object
resetToolPolicies, // Reset to defaults
registerToolPolicy, // Register policy at runtime
getToolPolicy, // Get policy for a tool
isRelaxedField, // Check if field has relaxed validation
getToolsByLevel, // List tools by security level
isValidSecurityLevel, // Validate security level string
defaultToolPolicies, // Default policy definitions
getToolPoliciesConfig, // Get current policies config
loadToolPoliciesConfig, // Load from file
matchesPattern, // Check if tool matches pattern
resolvePolicy, // Resolve policy for tool
ToolPolicyError, // Config error class
// Security presets
SECURITY_PRESETS, // All preset definitions
resolvePreset, // Get preset by name
getDefaultPreset, // Get default preset name
isValidPreset, // Validate preset name
// Type guards
isSeverity, // Type guard for Severity
isViolationType, // Type guard for ViolationType
isError, // Type guard for Error objects
getErrorMessage // Safe error message extraction
} from 'mcp-secure-server';
```
| Export | Description |
|--------|-------------|
| `SecureMcpServer` | Drop-in replacement for McpServer with 5-layer security |
| `SecureTransport` | Transport wrapper for message-level validation |
| `createSecureHttpServer` | HTTP server factory with security validation |
| `createSecureHttpHandler` | HTTP handler for composing multi-endpoint servers |
| `createSecureHttpsServer` | HTTPS server factory with TLS certificates |
| `ErrorRateLimiter` | Rate limiter for clients generating excessive errors |
| `getClientIp` | Extract client IP from request (X-Forwarded-For aware) |
| `ContextualValidationLayer` | Layer 5 class for advanced customization |
| `ContextualConfigBuilder` | Builder for Layer 5 configuration |
| `createContextualLayer` | Factory function for Layer 5 with defaults |
| `initializeToolPolicies` | Load tool policies from config object |
| `resetToolPolicies` | Reset to default (empty) policies |
| `registerToolPolicy` | Register single tool policy at runtime |
| `getToolPolicy` | Get resolved policy for a tool name |
| `isRelaxedField` | Check if a field has relaxed validation for a tool |
| `getToolsByLevel` | List tools registered at a specific security level |
| `isValidSecurityLevel` | Validate if a string is a valid security level |
| `defaultToolPolicies` | Default policy definitions for tools |
| `getToolPoliciesConfig` | Get current tool policies configuration |
| `loadToolPoliciesConfig` | Load policies from JSON file |
| `matchesPattern` | Check if tool name matches a pattern (glob-style) |
| `resolvePolicy` | Resolve merged policy for a tool from config |
| `ToolPolicyError` | Error class for config validation failures |
| `SECURITY_PRESETS` | Object containing all preset definitions (basic, standard, paranoid) |
| `resolvePreset` | Get preset configuration by name |
| `getDefaultPreset` | Get the default preset name ('standard') |
| `isValidPreset` | Validate if a string is a valid preset name |
| `isSeverity` | Type guard to check if value is a valid Severity |
| `isViolationType` | Type guard to check if value is a valid ViolationType |
| `isError` | Type guard to check if value is an Error object |
| `getErrorMessage` | Safely extract error message from unknown value |
### Subpath Imports
Everything is available from the root import, but two subpaths exist for consumers who
want a narrower dependency surface (e.g. bundlers tree-shaking a transport-only usage):
```typescript
import { SecureMcpServer } from 'mcp-secure-server/server'; // server class only
import { SecureTransport } from 'mcp-secure-server/transport'; // transport layer only
```
Both carry their own `types` conditions, so they resolve under `moduleResolution:
node16`/`nodenext`.
### HTTP Utilities
`ErrorRateLimiter` and `getClientIp` are the building blocks the HTTP factories use
internally — reach for them only when composing a custom handler around
`createSecureHttpHandler`:
```typescript
import { createSecureHttpHandler, ErrorRateLimiter, getClientIp } from 'mcp-secure-server';
const handler = createSecureHttpHandler(server); // (req, res) => Promise<void>
const limiter = new ErrorRateLimiter(); // throttles clients spamming invalid requests
http.createServer(async (req, res) => {
const ip = getClientIp(req); // X-Forwarded-For aware
if (limiter.shouldRateLimit(ip)) { res.writeHead(429).end(); return; }
await handler(req, res);
if (res.statusCode >= 400) limiter.recordError(ip); // count rejections against the client
});
```
## Layer 5 Customization
Layer 5 is enabled by default. You can add custom validators at runtime for application-specific security rules.
### Adding Custom Validators
```typescript
import { SecureMcpServer } from 'mcp-secure-server';
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{
contextual: {
domainRestrictions: {
enabled: true,
blockedDomains: ['evil.com']
}
}
}
);
// Access Layer 5
const layer5 = server.validationPipeline.layers[4];
// Add custom validator with priority (lower = runs first)
layer5.addValidator('sensitive-data-check', (message, context) => {
if (message.params?.arguments?.creditCard) {
return {
passed: false,
reason: 'Credit card data not allowed in requests',
severity: 'HIGH',
violationType: 'SENSITIVE_DATA'
};
}
return { passed: true };
}, { priority: 50, failOnError: true });
```
**Exceptions fail closed.** Since 0.0.21, `failOnError` defaults to `true` for validators,
global rules and response validators: a validator that throws blocks the request (or the
response) with `VALIDATOR_ERROR`, matching Layers 1–4. Pass `failOnError: false` for an
advisory validator whose failure should not block. Before 0.0.21 the default was `false`, so
a throwing authorization or rate-limit validator silently admitted the request.
### Adding Global Rules
Global rules run before validators and can short-circuit validation.
```typescript
layer5.addGlobalRule((message) => {
// Block specific operations
if (message.method === 'admin/delete-all') {
return {
passed: false,
reason: 'Operation not permitted',
severity: 'CRITICAL',
violationType: 'POLICY_VIOLATION'
};
}
return null; // null = pass, continue to validators
});
```
### Adding Response Validators
Validate responses before they're sent to clients.
```typescript
layer5.addResponseValidator('pii-filter', (response) => {
const content = JSON.stringify(response);
// Check for SSN pattern
if (/\d{3}-\d{2}-\d{4}/.test(content)) {
return {
passed: false,
reason: 'PII detected in response',
severity: 'HIGH',
violationType: 'DATA_LEAK'
};
}
return { passed: true };
});
```
### Using Context Store
Cross-request state management with TTL support.
```typescript
// Set context with 5-minute TTL
layer5.setContext('user:session:abc123', {
authenticated: true,
roles: ['admin']
}, 300000);
// Get context
const session = layer5.getContext('user:session:abc123');
// Use in validators
layer5.addValidator('auth-check', (message, context) => {
const session = layer5.getContext(`user:session:${context.sessionId}`);
if (!session?.authenticated) {
return {
passed: false,
reason: 'Authentication required',
severity: 'HIGH',
violationType: 'AUTH_REQUIRED'
};
}
return { passed: true };
});
```
### Disabling Layer 5
```typescript
const server = new SecureMcpServer(
{ name: 'my-server', version: '1.0.0' },
{ contextual: { enabled: false } }
);
```
## Security Features
See [SECURITY.md](https://github.com/aself101/mcp-secure-server/blob/main/SECURITY.md) for full security documentation including:
- Attack detection coverage (injection, XSS, SSRF, deserialization, etc.)
- Security best practices applied
- SSRF protection details
- Error sanitization
- Reporting vulnerabilities
## Attack Coverage
The framework detects and blocks 200+ attack patterns across 19 categories including injection attacks, path traversal, SSRF, deserialization, and more.
See [SECURITY.md](https://github.com/aself101/mcp-secure-server/blob/main/SECURITY.md#attack-vectors) for the complete threat model with:
- Attack vectors and examples
- Severity levels and detection layers
- Mitigation strategies
- Violation type reference
## Error Handling
### Error Message Behavior
Error messages are **deterministic and type-specific** — each violation type maps to a consistent, descriptive message. This makes errors diagnosable from logs, MCP clients, and LLM tool-use contexts.
| `enableDetailedErrors` | `error.message` | `data.reason` |
|------------------------|-----------------|---------------|
| `false` (default) | Type-specific (e.g. "Request validation failed") | Redacted internal reason |
| `true` | Type-specific + redacted reason appended | Redacted internal reason |
When `enableDetailedErrors` is `true`, the `error.message` field includes the redacted reason so the full diagnostic is visible without digging into the `data` envelope:
```json
{
"error": {
"code": -32602,
"message": "Request validation failed: Content validation — path traversal pattern detected in parameter 'file_path'",
"data": {
"reason": "Content validation — path traversal pattern detected in parameter 'file_path'",
"layer": "VALIDATION_ERROR"
}
}
}
```
When `enableDetailedErrors` is `false`, the top-level message is the category only — the detailed reason is still available in `data.reason`:
```json
{
"error": {
"code": -32602,
"message": "Request validation failed",
"data": {
"reason": "Content validation — path traversal pattern detected in parameter 'file_path'",
"layer": "VALIDATION_ERROR"
}
}
}
```
### Validation Error Structure
When the security pipeline blocks a request, the framework returns a JSON-RPC error with diagnostic context in the `data` field:
| Field | Description |
|-------|-------------|
| `timestamp` | When the error occurred (ISO 8601) |
| `token` | Unique error token for log correlation |
| `reason` | Redacted validation reason describing what failed and why |
| `layer` | Which validation layer or violation type triggered the block |
| `retryAfterMs` | Present only for `RATE_LIMIT_EXCEEDED` — milliseconds to wait |
The `reason` field is sanitized through the same credential/PII redaction pipeline used for logging, so it is safe to surface to clients while still providing actionable diagnostics.
### Error Codes
| Code | Violation Type | Meaning |
|------|---------------|---------|
| `-32602` | `VALIDATION_ERROR`, `POLICY_VIOLATION`, `CONTEXT_VIOLATION` | Invalid input or policy block |
| `-32000` | `RATE_LIMIT_EXCEEDED` | Too many requests — check `retryAfterMs` |
| `-32603` | `INTERNAL_ERROR` | Internal validation error |
### Severity Levels
| Severity | Description | Action |
|----------|-------------|--------|
| `CRITICAL` | Active exploit attempt (command injection, deserialization) | Block + Alert |
| `HIGH` | Serious attack (SQL injection, path traversal) | Block |
| `MEDIUM` | Suspicious activity (rate limit, size exceeded) | Block |
| `LOW` | Minor policy violation | Block or Warn |
### Outgoing Response Sanitization
The framework sanitizes outgoing JSON-RPC error responses that contain Zod validation patterns, with one important exception: **`-32602` (Invalid params) errors are preserved**. These errors describe the caller's input mistakes — field paths, expected types, and size limits — which are the input contract, not internal implementation details. Preserving them lets callers diagnose and fix their requests.
For non-`-32602` errors that contain Zod patterns (e.g., internal `-32603` errors), the framework replaces the response with a safe generic message:
```json
{
"error": {
"code": -32602,
"message": "Invalid input parameters",
"data": {
"reason": "Input failed schema validation (Zod). Check parameter types and required fields.",
"layer": "OUTGOING_SANITIZER"
}
}
}
```
This prevents internal Zod schema structures from leaking to clients while preserving actionable validation feedback on input errors.
### Type Guards for Error Handling
```typescript
import { isError, getErrorMessage, isSeverity } from 'mcp-secure-server';
try {
await server.connect(transport);
} catch (error) {
if (isError(error)) {
console.error('Error:', getErrorMessage(error));
}
}
// Validate severity values
const severity = 'HIGH';
if (isSeverity(severity)) {
// TypeScript knows severity is Severity type
}
```
## Claude Desktop Integration
Add to your Claude Desktop configuration (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"secure-server": {
"command": "node",
"args": ["path/to/your/server.js"],
"cwd": "/path/to/project"
}
}
}
```
### Test Server
Run the included test server to verify the framework:
```bash
npm start
```
The test server includes 7 protected tools:
- `debug-calculator` - Basic math operations
- `debug-file-reader` - Safe file reading
- `debug-echo` - Text echo service
- `debug-database` - Database query simulation
- `debug-http` - HTTP request simulation
- `debug-parser` - JSON/XML parsing
- `debug-image` - Image processing simulation
Add to Claude Desktop:
```json
{
"mcpServers": {
"secure-test": {
"command": "npx",
"args": ["tsx", "cookbook/minimal-server/minimal-test-server.ts"],
"cwd": "/path/to/mcp-secure-server"
}
}
}
```
## Development
### Running Tests
```bash
# Run all tests
npm test
# Run specific test suites
npm run test:unit
npm run test:integration
npm run test:performance
# Watch mode for development
npm run test:watch
# Generate coverage report
npm run test:coverage
```
**Test Coverage:**
- Overall: 93% lines, 88% branches (`npm run test:coverage`)
- 1250 tests, including `test/integration/dist-smoke.test.ts`, which imports the compiled `dist/` in a separate `node` process
- Mutation tests for severity levels
- Boundary value tests for limits
- Real attack vector validation
### Running a Single Test
```bash
npx vitest run test/unit/utils/canonical.test.js
```
### Linting
```bash
npm run lint
```
### Project Structure
```
src/
├── index.ts # Main entry point & public exports
├── types/ # TypeScript type definitions
│ ├── index.ts # Type exports & guards
│ ├── layers.ts # Layer type definitions
│ ├── messages.ts # MCP message types
│ ├── policies.ts # Policy type definitions
│ ├── server.ts # Server configuration types
│ └── validation.ts # Validation result types
└── security/
├── index.ts # Security module exports
├── mcp-secure-server.ts # SecureMcpServer class
├── constants.ts # Configuration constants
├── transport/
│ ├── index.ts # Transport exports
│ ├── secure-transport.ts # SecureTransport (stdio)
│ └── http-server.ts # HTTP transport server
├── layers/
│ ├── validation-layer-base.ts # Base class for all layers
│ ├── layer1-structure.ts # JSON-RPC validation
│ ├── layer2-content.ts # Content/injection detection
│ ├── layer2-validators/ # Modular content validators
│ │ ├── index.ts # Validator exports
│ │ ├── pattern-detection.ts # Attack pattern matching
│ │ ├── base64-css.ts # Base64/CSS attack detection
│ │ └── data-semantics.ts # Data format validation
│ ├── layer3-behavior.ts # Rate limiting & burst detection
│ ├── layer4-semantics.ts # Tool contracts & policies
│ ├── layer5-contextual.ts # Custom validators
│ ├── contextual-config-builder.ts # Layer 5 fluent configuration
│ └── layer-utils/
│ ├── content/
│ │ ├── canonicalize.ts # Text normalization
│ │ ├── unicode.ts # Unicode attack normalization
│ │ ├── dangerous-patterns.ts # Pattern configuration
│ │ ├── helper-utils.ts # Content helper functions
│ │ ├── patterns/ # Attack pattern definitions
│ │ │ ├── index.ts # Pattern exports & utilities
│ │ │ ├── injection.ts # SQL/XSS/NoSQL patterns
│ │ │ ├── path-traversal.ts # Path traversal patterns
│ │ │ ├── network.ts # SSRF/network patterns
│ │ │ └── overflow-validation.ts # Buffer/encoding patterns
│ │ └── utils/
│ │ ├── index.ts # Utility exports
│ │ ├── text-decoding.ts # Encoding detection
│ │ ├── hash-utils.ts # Cache key generation
│ │ └── structural-analysis.ts # Deep structure analysis
│ └── semantics/
│ ├── semantic-policies.ts # Tool/resource policies
│ └── semantic-quotas.ts # Quota management
└── utils/
├── validation-pipeline.ts # Multi-layer orchestration
├── security-logger.ts # Security event logging
├── error-sanitizer.ts # Safe error responses
├── request-normalizer.ts # Request normalization
├── response-validator.ts # Response validation
└── tool-registry.ts # Tool management
cookbook/ # Example MCP servers
├── http-server/ # HTTP transport example
├── multi-endpoint-server/ # Multi-endpoint routing
├── image-gen-server/ # Image generation APIs
├── kenpom-server/ # Sports analytics API
├── nba-server/ # NBA statistics API
├── api-wrapper-server/ # Safe external API wrapper
├── database-server/ # SQL injection prevention
├── filesystem-server/ # Path traversal prevention
├── cli-wrapper-server/ # Command injection prevention
├── monitoring-server/ # Security metrics & alerts
├── transaction-server/ # State machine workflows
└── advanced-validation-server/ # Advanced security demos
```
## Troubleshooting
### Module Not Found
```
Error: Cannot find module '@modelcontextprotocol/sdk'
```
**Solution:** `@modelcontextprotocol/sdk` is a regular dependency of this package, so this means a
broken install. Reinstall:
```bash
npm install mcp-secure-server
```
### Rate Limit Exceeded
```
Error: Request blocked: Rate limit exceeded
```
**Solution:** Increase rate limits in configuration:
```typescript
{
maxRequestsPerMinute: 60,
maxRequestsPerHour: 1000
}
```
### False Positive Detection
```
Error: Request blocked: Path traversal detected
```
**Solution:** If legitimate path contains `../`, configure resource policy:
```typescript
{
resourcePolicy: {
rootDirs: ['./allowed-paths'],
// Paths are validated relative to rootDirs
}
}
```
### Logging Not Working
```
getVerboseSecurityReport() returns empty
```
**Solution:** Enable logging in configuration:
```typescript
{
enableLogging: true,
verboseLogging: true
}
```
### Log Files Missing or Empty (logging enabled)
```
enableLogging: true, but no log files appear / getVerboseSecurityReport is empty
```
**Cause:** The logger silently degrades to no-op when the log directory cannot
be created or written — e.g. MCP hosts that launch the server with `cwd="/"`,
where the default `<cwd>/logs` resolves to the unwritable `/logs`.
**Diagnose:** Check `getSecurityStats().logger.fileLoggingAvailable`. If `false`,
logging is degraded; a one-time warning was also written to stderr at startup.
`writeErrors`/`lastWriteError` flag failures that began after a healthy start.
**Solution:** Point `logDir` at a writable path (or set the `LOG_DIR` env var):
```typescript
{
enableLogging: true,
logDir: '/var/log/my-mcp-server' // or set LOG_DIR=/var/log/my-mcp-server
}
```
If the directory still cannot be created, security events are not recorded but
the server continues operating.
### Layer 5 Validators Not Running
**Solution:** Ensure Layer 5 is enabled:
```typescript
{
contextual: {
enabled: true // Must be true (default)
}
}
```
### TypeScript Type Errors
**Solution:** Ensure you're using TypeScript 5.0+ with strict mode:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}
```
### SQL Injection Detected (False Positive)
```
Error: Request blocked: SQL injection detected
```
**Cause:** Content contains SQL-like keywords (SELECT, DROP, UNION) in legitimate text.
**Solutions:**
1. Use tool policies to relax validation for content storage tools:
```json
{
"version": "2.0",
"tools": {
"save_document": {
"level": "STORAGE",
"relaxedFields": ["content", "description"]
}
}
}
```
2. Set the tool's security level programmatically:
```typescript
import { registerToolPolicy } from 'mcp-secure-server';
registerToolPolicy('save_document', {
level: 'STORAGE',
relaxedFields: ['content']
});
```
### Command Injection Detected (False Positive)
```
Error: Request blocked: Command injection detected
```
**Cause:** Content contains shell characters (|, ;, &&, backticks) or common command names (top, env, curl, grep) in legitimate text.
**Note:** As of v0.0.14, STORAGE-level tools no longer check `command.systemInfo`, `command.basicInjection`, `command.networkOperations`, or `command.fileOperations` patterns — only `command.shellAccess` and `command.executionWrappers` (which match actual shell invocations like `bash -i`, `/bin/sh`, `system()`). If you're seeing this on a STORAGE-level tool, upgrade to v0.0.14+.
**Solution:** Use `STORAGE` level for content tools:
```typescript
registerToolPolicy('save_code_snippet', {
level: 'STORAGE',
relaxedFields: ['code']
});
```
### XSS Attempt Detected (False Positive)
```
Error: Request blocked: XSS attempt detected
```
**Cause:** Content contains HTML/script tags in legitimate documentation.
**Solution:** Use `DISPLAY` level for read-only display tools:
```typescript
registerToolPolicy('render_markdown', {
level: 'DISPLAY'
});
```
### Semantic Validation: Missing Required Parameter
```
Error: Request blocked: Missing required parameter
```
**Cause:** Tool is registered in `toolRegistry` with `argsShape` that doesn't match the request.
**Solution:** Verify tool schema matches expected parameters:
```typescript
{
toolRegistry: [{
name: 'my-tool',
argsShape: {
query: { type: 'string', required: true },
limit: { type: 'number' } // optional
}
}]
}
```
### Semantic Validation: Tool Not Registered
```
Error: Request blocked: Tool not registered
```
**Cause:** Tool called via MCP but not in server's `toolRegistry`.
**Solutions:**
1. Register all tools you expose:
```typescript
{
toolRegistry: [
{ name: 'tool-a', sideEffects: 'none' },
{ name: 'tool-b', sideEffects: 'read' }
]
}
```
2. There is no "allow all" setting. Unregistered tools are always denied at Layer 4
(`TOOL_NOT_ALLOWED`, fail closed). Omitting `toolRegistry` does not lift that — it
installs the built-in registry, which contains only `debug-calculator`,
`debug-file-reader` and `debug-echo`. *(Until 0.0.21 this section claimed omission
allowed all tools; it never did.)*
### Message Size Exceeded
```
Error: Request blocked: Message size exceeds limit
```
**Solution:** Increase message size limit:
```typescript
{
maxMessageSize: 100000 // 100KB (default: 50KB)
}
```
### String Parameter Too Long
```
Error: Request could not be processed: String parameter too long: 9213 chars (max: 5000)
```
**Cause:** A single string argument exceeds the per-string cap (default: 5,000 chars). Common for tools that legitimately accept long text payloads — report markdown, document bodies, large descriptions.
**Solution:** Raise the per-string limit, and keep `maxMessageSize` above it — the message envelope is larger than the string it carries, so the size check fires first if you raise only the string cap:
```typescript
{
maxStringLength: 128 * 1024, // 128K chars (default: 5,000)
maxMessageSize: 500 * 1024 // envelope headroom above maxStringLength
}
```
Requires `mcp-secure-server >= 0.0.17-security` — earlier versions hardcode the default.
### Burst Activity Detected
```
Error: Request blocked: Burst activity detected
```
**Cause:** Too many requests in the burst detection window.
**Solutions:**
1. Increase burst threshold:
```typescript
{
burstThreshold: 20 // Max requests in window (default: 10)
}
```
2. Extend the burst window:
```typescript
{
burstWindowMs: 15000 // 15 seconds (default: 10000)
}
```
3. Use the `basic` preset for development:
```typescript
{
securityLevel: 'basic' // 30 burst threshold, 10s window
}
```
### Automated Timing Pattern Detected
```
Error: Request blocked: Automated timing pattern detected
```
**Cause:** Requests are arriving at suspiciously consistent intervals, suggesting automation.
**Solutions:**
1. Disable automation detection (for legitimate automation):
```typescript
{
automationDetection: { enabled: false }
}
```
2. Adjust detection thresholds:
```typescript
{
automationDetection: {
enabled: true,
maxVariance: 100, // Allow more timing variance (default: 50ms)
sampleSize: 10 // Require more samples (default: 5)
}
}
```
3. Use the `basic` preset (automation detection disabled):
```typescript
{
securityLevel: 'basic'
}
```
### Suspiciously Large Message
```
Error: Request blocked: Suspiciously large message
```
**Cause:** Message exceeds the suspicious size threshold.
**Solution:** Increase the threshold:
```typescript
{
suspiciousMessageSize: 50000 // 50KB (default: 20KB)
}
```
## License
MIT License - see [LICENSE](LICENSE) file for details.
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
---
**Disclaimer:** This framework provides defense-in-depth security but cannot guarantee protection against all attacks. Always follow security best practices and keep dependencies updated.
TDQS
A3.5/5.0
Scored across 7 tools
Disambiguation5/5
Each tool has a distinct domain (calculator, database, echo, file reading, HTTP, image processing, parsing) with no functional overlap, making them easy to distinguish.
Naming Consistency5/5
All tools follow a consistent 'debug-<noun>' pattern with hyphens, providing a clear and predictable naming convention.
Tool Count5/5
Seven tools is well-scoped for a debugging/testing framework, covering essential operations without excess.
Completeness5/5
The tools cover a broad range of typical debugging scenarios (arithmetic, database, file I/O, HTTP, image processing, parsing) with safety restrictions, leaving no obvious gaps.
Maintenance
ActivityMaintained
ResponsivenessNo issues