@aashari/boilerplate-mcp-server
Version:
TypeScript MCP server boilerplate with STDIO and HTTP transport support, CLI tools, and extensible architecture
501 lines (384 loc) ⢠14.9 kB
Markdown
# MCP Best Practices Audit Report
**Date:** 2025-01-13
**Project:** boilerplate-mcp-server v1.18.0
**Auditor:** AI Agent (based on official MCP SDK documentation and examples)
**SDK Version:** @modelcontextprotocol/sdk v1.25.3
---
## Executive Summary
The boilerplate-mcp-server demonstrates **strong architectural patterns** and follows most modern MCP best practices. However, there are **critical security gaps** that must be addressed before production deployment.
**Overall Compliance:** 70% ā
Good | 30% ā ļø Needs Improvement
**Priority Findings:**
- š“ **CRITICAL**: Missing DNS rebinding protection (no Origin header validation)
- š” **HIGH**: Missing explicit localhost binding
- š” **HIGH**: Missing `isError` field in tool error responses
- š¢ **MEDIUM**: Missing ResourceLink pattern implementation
- š¢ **LOW**: Missing prompt registration example
---
## Detailed Findings
### 1. Security & Transport š
#### 1.1 DNS Rebinding Protection ā CRITICAL
**Current State:**
```typescript
// src/index.ts (Line 70-72)
const app = express();
app.use(cors());
app.use(express.json());
```
**Issue:**
Using plain `express()` without DNS rebinding protection. The official MCP transport specification states:
> "Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks."
**Best Practice:**
```typescript
import { createMcpExpressApp } from '@modelcontextprotocol/express';
// Built-in DNS rebinding protection
const app = createMcpExpressApp();
```
**Impact:**
- **Severity:** HIGH
- **Risk:** Malicious websites can potentially make requests to localhost MCP server
- **Attack Vector:** DNS rebinding attack targeting user's localhost
**Recommendation:**
Add `@modelcontextprotocol/express` dependency and replace `express()` with `createMcpExpressApp()`.
**Reference:**
- [MCP Transport Security](https://modelcontextprotocol.io/docs/concepts/transports)
- [SDK Server Docs](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md)
---
#### 1.2 Localhost Binding ā ļø HIGH
**Current State:**
```typescript
// src/index.ts (Line 107-113)
const PORT = Number(process.env.PORT ?? 3000);
await new Promise<void>((resolve) => {
app.listen(PORT, () => {
serverLogger.info(
`HTTP transport listening on http://localhost:${PORT}${mcpEndpoint}`,
);
resolve();
});
});
```
**Issue:**
`app.listen(PORT)` without explicit hostname binds to `0.0.0.0` (all interfaces) on some systems.
**Best Practice:**
```typescript
app.listen(PORT, '127.0.0.1', () => {
serverLogger.info(`HTTP transport listening on http://127.0.0.1:${PORT}${mcpEndpoint}`);
resolve();
});
```
**Impact:**
- **Severity:** MEDIUM-HIGH
- **Risk:** Server accessible from network instead of localhost-only
- **Attack Vector:** Remote access if firewall misconfigured
**Recommendation:**
Explicitly bind to `127.0.0.1` or `localhost` to prevent network exposure.
---
#### 1.3 Authentication ā ļø MEDIUM
**Current State:**
No authentication mechanism implemented in HTTP transport.
**Issue:**
MCP transport documentation recommends:
> "Servers SHOULD implement authentication and authorization mechanisms appropriate for their deployment context."
**Best Practice Examples:**
- Bearer token authentication
- OAuth 2.0 integration
- API key validation
**Impact:**
- **Severity:** MEDIUM (depends on deployment)
- **Risk:** Unauthorized access if server exposed beyond localhost
- **Mitigation:** Low risk if DNS rebinding protection + localhost binding implemented
**Recommendation:**
Document authentication requirements in README and provide example implementation pattern.
---
### 2. Error Handling š ļø
#### 2.1 Missing `isError` Field ā ļø HIGH
**Current State:**
```typescript
// src/utils/error.util.ts (Line 129-166)
export function formatErrorForMcpTool(error: unknown): {
content: Array<{ type: 'text'; text: string }>;
metadata?: {
errorType: ErrorType;
statusCode?: number;
errorDetails?: unknown;
};
} {
// ... returns content with metadata but no isError field
}
```
**Issue:**
According to MCP SDK examples and best practices, tool error responses should include `isError: true` at the top level:
```typescript
// From typescript-sdk examples
return {
content: [{ type: 'text', text: errorMessage }],
isError: true // ā Missing in our implementation
};
```
**Best Practice:**
```typescript
export function formatErrorForMcpTool(error: unknown): {
content: Array<{ type: 'text'; text: string }>;
isError: true; // ā Mark explicitly as error
metadata?: {
errorType: ErrorType;
statusCode?: number;
errorDetails?: unknown;
};
} {
const mcpError = ensureMcpError(error);
return {
content: [{ type: 'text', text: `Error: ${mcpError.message}` }],
isError: true, // ā Add this field
metadata: { /* ... */ }
};
}
```
**Impact:**
- **Severity:** MEDIUM-HIGH
- **Risk:** MCP clients may not properly detect error state
- **Behavior:** Clients might treat errors as successful responses
**Recommendation:**
Add `isError: true` to error response type and all error formatters.
---
### 3. Modern API Usage ā
EXCELLENT
#### 3.1 Tool Registration ā
COMPLIANT
**Current Implementation:**
```typescript
// src/tools/ipaddress.tool.ts (Line 85-95)
server.registerTool(
'ip_get_details',
{
title: 'IP Address Lookup', // ā
Display name
description: IP_GET_DETAILS_DESCRIPTION, // ā
Detailed description
inputSchema: GetIpDetailsToolSchema, // ā
Zod schema
},
handleGetIpDetails,
);
```
**Assessment:**
ā
**EXCELLENT** - Follows modern SDK v1.22.0+ API patterns perfectly:
- Uses `registerTool()` instead of deprecated `tool()` method
- Provides both `title` (for UI display) and `description` (detailed info)
- Uses Zod for schema validation
- Separates handler logic cleanly
**Best Practice Alignment:**
Matches official examples from modelcontextprotocol/typescript-sdk repository.
---
#### 3.2 Resource Registration ā
COMPLIANT
**Current Implementation:**
```typescript
// src/resources/ipaddress.resource.ts (Line 17-50)
server.registerResource(
'ip-lookup',
new ResourceTemplate('ip://{ipAddress}', { list: undefined }),
{
title: 'IP Address Lookup',
description: 'Retrieve geolocation and network information for a public IP address',
},
async (uri, variables) => {
// Handler implementation
},
);
```
**Assessment:**
ā
**EXCELLENT** - Modern resource registration with:
- `ResourceTemplate` for parameterized URIs
- Proper title + description metadata
- Clean handler with URI variables extraction
---
#### 3.3 Transport Implementation ā
COMPLIANT
**Current Implementation:**
```typescript
// src/index.ts (Line 77-80)
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // ā
Stateless pattern
});
```
**Assessment:**
ā
**EXCELLENT** - Uses modern transport:
- `StreamableHTTPServerTransport` (not deprecated HTTP+SSE)
- Explicit stateless configuration
- Proper async/await patterns
**Note:**
Deprecated HTTP+SSE transport correctly avoided. Current implementation uses recommended Streamable HTTP protocol.
---
### 4. Missing Features š
#### 4.1 ResourceLink Pattern ā ļø MEDIUM
**Current State:**
Tools return full content inline, no support for ResourceLink references.
**Best Practice:**
```typescript
// Tools can reference resources instead of embedding content
return {
content: [
{
type: 'resource',
resource: {
uri: 'ip://8.8.8.8',
text: 'Lookup result available as resource'
}
}
]
};
```
**Benefit:**
- Reduces token usage for large responses
- Enables resource caching
- Allows clients to fetch details on-demand
**Impact:**
- **Severity:** LOW-MEDIUM (optimization opportunity)
- **Risk:** Higher token costs for large responses
- **Migration:** Optional enhancement
**Recommendation:**
Document ResourceLink pattern in MODERNIZATION.md and provide example implementation.
---
#### 4.2 Prompt Registration ā ļø LOW
**Current State:**
No prompt registration examples in boilerplate.
**Best Practice:**
```typescript
server.registerPrompt(
'ip-analysis',
{
title: 'IP Analysis Template',
description: 'Generate structured analysis for IP address',
arguments: [
{ name: 'ip', description: 'IP address to analyze', required: true }
]
},
async (vars) => {
const ipData = await getIpDetails(vars.ip);
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Analyze this IP: ${JSON.stringify(ipData)}`
}
}
]
};
}
);
```
**Impact:**
- **Severity:** LOW (not essential for all servers)
- **Risk:** None (prompts are optional MCP feature)
- **Use Case:** Helpful for AI-driven analysis workflows
**Recommendation:**
Add example prompt registration to demonstrate MCP's three primitive types (tools, resources, prompts).
---
#### 4.3 Protocol Version Header ā ļø LOW
**Current State:**
No mention of `MCP-Protocol-Version` header handling in documentation.
**Best Practice:**
According to transport spec:
> "Client MUST include MCP-Protocol-Version header on all requests"
**Note:**
SDK likely handles this automatically, but should be documented.
**Recommendation:**
Document protocol version requirements in README technical details section.
---
### 5. Strengths šŖ
The following aspects demonstrate excellent MCP implementation:
#### 5.1 Architecture ā
- **6-Layer Pattern:** CLI ā Tools ā Resources ā Controllers ā Services ā Utils
- **Clean Separation:** Business logic isolated from MCP concerns
- **Type Safety:** Full TypeScript with strict mode
- **DI Pattern:** Controller dependencies clearly defined
#### 5.2 Developer Experience ā
- **Dual Transport:** Supports both STDIO and HTTP seamlessly
- **CLI Mode:** Built-in CLI for testing without MCP clients
- **Structured Logging:** Context-aware logging throughout
- **Error Classification:** Typed error system with meaningful categories
#### 5.3 Quality ā
- **Testing:** 47 tests passing across 6 test suites
- **Graceful Shutdown:** SIGINT/SIGTERM handlers implemented
- **Health Check:** HTTP mode includes `/` endpoint for monitoring
- **Documentation:** Comprehensive README, CHANGELOG, and MODERNIZATION guide
#### 5.4 Modern Dependencies ā
- **MCP SDK:** v1.25.3 (latest stable)
- **Zod:** v4.3.6 (latest with new features)
- **Express:** v5.2.1 (latest major version)
- **All deps current:** Updated 2025-01-13
---
## Priority Recommendations
### Immediate (Before Production)
1. **š“ Add DNS Rebinding Protection**
```bash
npm install @modelcontextprotocol/express
```
Replace `express()` with `createMcpExpressApp()` in [src/index.ts](src/index.ts#L70-L72)
2. **š“ Explicit Localhost Binding**
Update `app.listen()` to bind to `127.0.0.1` explicitly
3. **š” Add `isError: true` Field**
Update [src/utils/error.util.ts](src/utils/error.util.ts#L129-166) error formatters
---
### Short Term (Next Release)
4. **š¢ Document Authentication Requirements**
Add security section to README with authentication best practices
5. **š¢ Add ResourceLink Example**
Create example tool that returns resource references instead of inline content
6. **š¢ Add Prompt Registration Example**
Demonstrate all three MCP primitives (tools, resources, prompts)
---
### Long Term (Future Enhancement)
7. **āŖ MCP v2 Migration Plan**
SDK v2 expected Q1 2026 - prepare migration guide
8. **āŖ Task Execution Pattern**
Add long-running task example with progress reporting
9. **āŖ OAuth Integration Example**
Demonstrate secure authentication pattern for HTTP transport
---
## Testing Validation ā
All tests passing after dependency updates:
```
Test Suites: 6 passed, 6 total
Tests: 47 passed, 47 total
Snapshots: 0 total
Time: 8.204s
```
**Coverage:**
- ā
CLI commands functional
- ā
Tool handlers functional
- ā
Resource handlers functional
- ā
Controllers functional
- ā
Services functional
- ā
Utilities functional
---
## Compliance Summary
| Category | Status | Notes |
|----------|--------|-------|
| **Transport** | ā
Compliant | Modern Streamable HTTP |
| **Tool Registration** | ā
Compliant | Modern registerTool API |
| **Resource Registration** | ā
Compliant | ResourceTemplate pattern |
| **DNS Rebinding Protection** | ā Missing | Critical security gap |
| **Localhost Binding** | ā ļø Partial | Needs explicit binding |
| **Authentication** | ā ļø Not Implemented | Recommended for HTTP |
| **Error Handling** | ā ļø Partial | Missing isError field |
| **Session Management** | ā
Compliant | Stateless pattern |
| **Graceful Shutdown** | ā
Compliant | SIGINT/SIGTERM handled |
| **Documentation** | ā
Excellent | Comprehensive guides |
| **Testing** | ā
Excellent | 47 tests passing |
| **Dependencies** | ā
Current | All up-to-date (2025-01-13) |
---
## Conclusion
The boilerplate-mcp-server is a **well-architected, well-documented project** that follows most MCP best practices. The modern API usage, clean architecture, and comprehensive testing demonstrate high-quality engineering.
However, **DNS rebinding protection is a critical security gap** that must be addressed before production deployment. Once the security recommendations are implemented, this boilerplate will be production-ready and an excellent foundation for MCP server development.
**Recommended Actions:**
1. Implement DNS rebinding protection (CRITICAL)
2. Add explicit localhost binding (HIGH)
3. Update error formatters with `isError` field (HIGH)
4. Document authentication requirements (MEDIUM)
5. Add ResourceLink and prompt examples (NICE-TO-HAVE)
---
## References
1. [MCP Transport Specification](https://modelcontextprotocol.io/docs/concepts/transports)
2. [MCP TypeScript SDK - Server Docs](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md)
3. [MCP TypeScript SDK - Examples](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples)
4. [MCP Security Best Practices](https://modelcontextprotocol.io/docs/concepts/security)
5. [Streamable HTTP Protocol](https://modelcontextprotocol.io/docs/concepts/transports#streamable-http)
**Audit Version:** 1.0
**Next Review:** After MCP SDK v2 release (Q1 2026)