Purpose
Audit MCP (Model Context Protocol) servers for security issues, architectural violations, missing validation, and production readiness. Ensure servers follow SDK best practices.
When to Use This Skill
- Reviewing MCP server code in PRs
- Auditing existing MCP servers for quality
- Pre-release security and quality checks
- Verifying MCP servers follow best practices
When NOT to Use This Skill
- Writing new MCP servers (use mcp-server-writing)
- General TypeScript/Python code review (use language-specific reviewers)
- Reviewing non-MCP server code
Severity Classification
| Severity | Description | Action |
|---|---|---|
| CRITICAL | Security vulnerabilities, secrets exposure, data leaks | Block PR, fix immediately |
| HIGH | Missing validation, error swallowing, transport violations | Block PR, require fix |
| MEDIUM | Missing descriptions, poor error messages, logging issues | Request fix before merge |
| LOW | Style issues, optimization opportunities, minor improvements | Suggest improvements |
Automated Detection Commands
Run these commands to detect common violations. Each command includes the violation code for reference.
Security Violations (S1-S5)
S1: Hardcoded Secrets
grep -rn "api[_-]\?key\s*[:=]" --include="*.ts" --include="*.py" src/ | grep -v "process\.env\|os\.environ"
S2: Console.log to stdout (breaks stdio transport)
grep -rn "console\.log\|print(" --include="*.ts" --include="*.py" src/ | grep -v "console\.error\|sys\.stderr\|\.test\.\|\.spec\."
S3: Missing ReDoS Protection
grep -rn "new RegExp\|\.match\|\.replace\|\.split" --include="*.ts" --include="*.py" src/ | grep -v "safeInput\|MAX_INPUT"
S4: Unsafe eval/exec
grep -rn "eval(\|exec(\|Function(" --include="*.ts" --include="*.py" src/
S5: SQL Injection Risk
grep -rn "query.*\`\|execute.*f\"\|cursor\.execute.*%" --include="*.ts" --include="*.py" src/
Architecture Violations (A1-A5)
A1: Missing Tool Descriptions
grep -rn "name:\s*['\"]" --include="*.ts" src/ -A 3 | grep -B 3 "inputSchema" | grep -v "description"
A2: Missing inputSchema Property Descriptions
grep -rn "properties:" --include="*.ts" src/ -A 10 | grep -E "type.*string|type.*number" | grep -v "description"
A3: Tools Without Validation
grep -rn "request\.params\|args\." --include="*.ts" src/ | grep -v "validate\|ajv\|zod\|schema"
A4: Missing isError Flag
grep -rn "content.*error\|success.*false" --include="*.ts" src/ | grep -v "isError"
A5: Direct Response Without JSON
grep -rn "text:.*\`" --include="*.ts" src/ | grep -v "JSON\.stringify"
Error Handling Violations (E1-E5)
E1: Empty Catch Blocks
grep -rn "catch.*{" --include="*.ts" --include="*.py" src/ -A 2 | grep -E "^\s*}\s*$"
E2: Swallowed Errors (catch without rethrow or return)
grep -rn "catch" --include="*.ts" src/ -A 5 | grep -v "throw\|return\|logger\|console\.error"
E3: Missing Error Context
grep -rn "throw new Error\|raise.*Error" --include="*.ts" --include="*.py" src/ | grep -v ":\|context\|failed"
E4: Generic Error Messages
grep -rn "\"error\"\|\"Error\"\|\"failed\"" --include="*.ts" src/ | grep -v "suggestion\|field\|message"
E5: Unhandled Promise Rejections
grep -rn "\.then(" --include="*.ts" src/ | grep -v "\.catch\|await"
Maintainability Violations (M1-M5)
M1: Magic Numbers/Strings
grep -rn "[0-9]\{4,\}\|timeout.*[0-9]" --include="*.ts" --include="*.py" src/ | grep -v "const\|MAX_\|MIN_\|DEFAULT_"
M2: Missing Type Annotations (Python)
grep -rn "def.*(" --include="*.py" src/ | grep -v "->.*:"
M3: TODO/FIXME in Production Code
grep -rn "TODO\|FIXME\|XXX\|HACK" --include="*.ts" --include="*.py" src/
M4: Duplicate Tool Definitions
grep -rn "name:\s*['\"]" --include="*.ts" src/ | cut -d: -f3 | sort | uniq -d
M5: Inconsistent Error Response Format
grep -rn "isError.*true" --include="*.ts" src/ -B 5 | grep "text:" | grep -v "success\|errors"
Manual Review Checklist
Security Review
- S1: No hardcoded API keys, passwords, or secrets
- S2: All logging goes to stderr (not stdout)
- S3: Input length limited before regex processing
- S4: No eval(), exec(), or dynamic code execution
- S5: Parameterized queries for any database access
- S6: Environment variables used for configuration
- S7: Sensitive data not logged (passwords, tokens, PII)
Architecture Review
- A1: All tools have clear
descriptionfields - A2: All inputSchema properties have
description - A3: Input validation before any processing
- A4: Error responses include
isError: true - A5: All responses are JSON-serialized
- A6: Resources use appropriate URI schemes
- A7: Prompts have clear argument descriptions
Error Handling Review
- E1: No empty catch blocks
- E2: Errors logged or re-thrown, never swallowed
- E3: Error messages include context (what failed, why)
- E4: Error responses include
suggestionfield - E5: Async operations properly awaited/handled
- E6: Graceful degradation for external dependencies
Production Readiness Review
- P1: Structured logging with JSON format
- P2: Health check endpoint or mechanism
- P3: Configurable timeouts for external calls
- P4: Graceful shutdown handling
- P5: No debug/development code in production
- P6: Dependencies pinned to specific versions
- P7: Dockerfile or deployment configuration present
Common Violations with Fixes
S2: Logging to stdout (CRITICAL)
Problem: stdout is reserved for JSON-RPC messages. Logging there corrupts the transport.
// ❌ BAD: Breaks MCP stdio transport
console.log("Processing request:", args);
// ✅ GOOD: Log to stderr
console.error(
JSON.stringify({
timestamp: new Date().toISOString(),
level: "INFO",
message: "Processing request",
context: { args_keys: Object.keys(args) },
}),
);
S3: Missing ReDoS Protection (HIGH)
Problem: Unbounded input to regex can cause catastrophic backtracking.
// ❌ BAD: No input length limit
const matches = userInput.match(/complex.*pattern/);
// ✅ GOOD: Limit input before regex
const MAX_INPUT_LENGTH = 50_000;
const safeInput = (text: string) =>
text.length > MAX_INPUT_LENGTH ? text.slice(0, MAX_INPUT_LENGTH) : text;
const matches = safeInput(userInput).match(/complex.*pattern/);
A1: Missing Tool Description (MEDIUM)
Problem: LLM can't understand when to use the tool.
// ❌ BAD: No description
{
name: "process_data",
inputSchema: { type: "object", properties: { data: { type: "string" } } }
}
// ✅ GOOD: Clear description explaining purpose and return value
{
name: "process_data",
description: "Validates and transforms input data according to specified format. Returns structured result with validation errors if any.",
inputSchema: {
type: "object",
properties: {
data: {
type: "string",
description: "Raw data string to process"
},
format: {
type: "string",
enum: ["json", "csv", "xml"],
description: "Expected input format for validation"
}
},
required: ["data", "format"]
}
}
A4: Missing isError Flag (HIGH)
Problem: Client can't distinguish errors from successful responses.
// ❌ BAD: Error without isError flag
return {
content: [{ type: "text", text: JSON.stringify({ error: "Not found" }) }],
};
// ✅ GOOD: Proper error response
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
errors: [
{
field: "id",
message: "Resource not found",
suggestion: "Verify the ID exists and try again",
},
],
}),
},
],
isError: true,
};
E1: Empty Catch Block (HIGH)
Problem: Errors are silently swallowed, making debugging impossible.
// ❌ BAD: Silent failure
try {
await riskyOperation();
} catch (error) {
// do nothing
}
// ✅ GOOD: Log and handle appropriately
try {
await riskyOperation();
} catch (error) {
logger.error("Risky operation failed", { error: error.message });
return createErrorResponse([
{
field: "operation",
message: "Operation failed",
suggestion: "Check input parameters and retry",
},
]);
}
E4: Generic Error Messages (MEDIUM)
Problem: Users can't understand what went wrong or how to fix it.
// ❌ BAD: Unhelpful error
return { error: "Something went wrong" };
// ✅ GOOD: Specific, actionable error
return {
success: false,
errors: [
{
field: "partner_id",
message: "Invalid UUID format: 'abc123'",
suggestion: "Use format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
},
],
};
Review Report Template
Use this template when reporting review findings:
# MCP Server Review: [Server Name]
**Reviewer:** [Name]
**Date:** [Date]
**Files Reviewed:** [List of files]
## Summary
| Severity | Count |
| -------- | ----- |
| CRITICAL | X |
| HIGH | X |
| MEDIUM | X |
| LOW | X |
**Recommendation:** [APPROVE / APPROVE WITH CHANGES / REQUEST CHANGES / BLOCK]
## Critical Issues
### [S1] Hardcoded API Key
- **File:** src/config.ts:42
- **Code:** `const API_KEY = "sk_live_..."`
- **Fix:** Move to environment variable
## High Issues
### [A4] Missing isError Flag
- **File:** src/tools/processor.ts:128
- **Code:** Error response without isError
- **Fix:** Add `isError: true` to error responses
## Medium Issues
### [A1] Missing Tool Description
- **File:** src/index.ts:56
- **Tool:** `validate_input`
- **Fix:** Add description explaining tool purpose
## Low Issues
### [M3] TODO Comment
- **File:** src/utils/parser.ts:89
- **Comment:** `// TODO: optimize this`
- **Suggestion:** Create ticket or remove
## Checklist Results
- [x] Security: 6/7 passed
- [x] Architecture: 5/7 passed
- [ ] Error Handling: 4/6 passed
- [x] Production: 7/7 passed
Workflow
Step 1: Run Automated Detection
# Run all security checks
grep -rn "console\.log\|api[_-]\?key\s*[:=]" --include="*.ts" src/
# Run all architecture checks
grep -rn "name:\s*['\"]" --include="*.ts" src/ -A 5 | grep -v description
Step 2: Manual Checklist Review
Walk through each section of the manual review checklist, marking items as pass/fail.
Step 3: Code Walkthrough
- Read each tool definition
- Trace request handling flow
- Verify error paths
- Check resource and prompt definitions
Step 4: Generate Report
Use the review report template to document findings with specific file locations and code snippets.
Step 5: Classify and Prioritize
- Group findings by severity
- Identify blocking issues (CRITICAL/HIGH)
- Suggest fixes for each issue
Related Skills
- mcp-server-writing - Create new MCP servers
- security-scan - General security scanning
- quality-check - Code quality checks
Resources
- MCP Best Practices
- MCP FAQs
- See REFERENCE.md for complete violation catalog
- See CHECKLISTS/ for printable audit checklists