Skip to content

Commit 433cf28

Browse files
yigitkonurclaude
andcommitted
Setup bullet-proof TypeScript + ESLint + Prettier configuration
- Migrated to flat ESLint config with strict type-only imports enforcement - Added comprehensive async safety rules and type checking - Configured minimal Prettier with high-signal settings only - Setup pre-commit hooks with husky and lint-staged - Added GitHub Actions CI workflow for automated validation - Pinned Node types explicitly and updated tsconfig for NodeNext modules - Fixed all ESLint warnings and type safety issues - Cleaned up unused config files and dependencies 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b9ad2b5 commit 433cf28

File tree

15 files changed

+1303
-399
lines changed

15 files changed

+1303
-399
lines changed

.eslintrc.json

Lines changed: 0 additions & 22 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: ci
2+
on:
3+
push: { branches: [main] }
4+
pull_request:
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- uses: actions/setup-node@v4
11+
with: { node-version: 22, cache: 'npm' }
12+
- run: npm ci
13+
- run: npm run typecheck
14+
- run: npm run lint:ci
15+
- run: npm run format:check
16+
- run: npm run build

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npm exec lint-staged

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
node_modules/
3+
coverage/

.prettierrc.json

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
{
22
"printWidth": 100,
3-
"tabWidth": 2,
4-
"useTabs": false,
5-
"semi": true,
63
"singleQuote": true,
7-
"quoteProps": "as-needed",
8-
"jsxSingleQuote": false,
94
"trailingComma": "all",
10-
"bracketSpacing": true,
11-
"bracketSameLine": false,
12-
"arrowParens": "always",
13-
"requirePragma": false,
14-
"insertPragma": false,
15-
"proseWrap": "preserve",
16-
"htmlWhitespaceSensitivity": "css",
17-
"vueIndentScriptAndStyle": false,
18-
"endOfLine": "lf",
19-
"embeddedLanguageFormatting": "auto",
20-
"singleAttributePerLine": false
21-
}
5+
"semi": true
6+
}

CLAUDE.md

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
77
This is an **Educational Reference Implementation** of a Stateless HTTP Streamable MCP Server. This is NOT just a simple example - it's a comprehensive teaching resource designed to demonstrate production-ready patterns, security best practices, and modern deployment strategies for Model Context Protocol servers.
88

99
### Educational Mission
10+
1011
This repository serves as a **masterclass** in building stateless MCP servers, covering:
12+
1113
- **Architecture Principles**: True stateless design enabling infinite scaling
1214
- **Security Engineering**: DNS rebinding protection, rate limiting, error sanitization
1315
- **SDK Integration**: Trust the SDK for protocol concerns, avoid redundant validation
@@ -17,30 +19,34 @@ This repository serves as a **masterclass** in building stateless MCP servers, c
1719
### Core Architecture Patterns
1820

1921
#### 1. Fresh Instance Per Request (The Golden Rule)
22+
2023
```typescript
2124
// In handleMCPRequest() - this happens for EVERY request:
22-
const server = createMCPServer(); // 1. Fresh server instance
25+
const server = createMCPServer(); // 1. Fresh server instance
2326
const transport = new StreamableHTTPServerTransport({
24-
sessionIdGenerator: undefined, // 2. Stateless mode (critical!)
25-
enableDnsRebindingProtection: true, // 3. Security by design
27+
sessionIdGenerator: undefined, // 2. Stateless mode (critical!)
28+
enableDnsRebindingProtection: true, // 3. Security by design
2629
});
27-
await server.connect(transport); // 4. Connect ephemeral instances
28-
await transport.handleRequest(req, res); // 5. Process single request
30+
await server.connect(transport); // 4. Connect ephemeral instances
31+
await transport.handleRequest(req, res); // 5. Process single request
2932
// 6. Cleanup happens in res.on('close') listener
3033
```
3134

3235
#### 2. SDK Trust Principle
36+
3337
- **DO**: Let `StreamableHTTPServerTransport` handle protocol validation internally
3438
- **DON'T**: Create custom middleware to duplicate SDK validation logic
3539
- **WHY**: SDK is the source of truth; duplicating creates maintenance burden
3640

3741
#### 3. Security-First Design
42+
3843
- DNS rebinding protection (mandatory for local servers)
3944
- Rate limiting (1000 requests per 15-minute window)
4045
- Production error sanitization (hide stack traces)
4146
- Request size validation before JSON parsing
4247

4348
#### 4. Clean Code Over Optimization
49+
4450
- Simple object creation instead of object pooling
4551
- Idiomatic TypeScript patterns
4652
- Clear, maintainable code structure
@@ -49,13 +55,15 @@ await transport.handleRequest(req, res); // 5. Process single request
4955
## Key Implementation Details
5056

5157
### Server Configuration
58+
5259
- **Port**: Always 1071 (not 3000)
5360
- **Architecture**: Stateless HTTP + SSE streaming
5461
- **Transport**: `StreamableHTTPServerTransport` with `sessionIdGenerator: undefined`
5562
- **Security**: DNS rebinding protection enabled by default
5663
- **Logging**: Structured JSON with request correlation via `requestId`
5764

5865
### File Structure
66+
5967
```
6068
src/
6169
├── types.ts # Data contracts (schemas, constants, interfaces)
@@ -70,27 +78,31 @@ src/
7078
```
7179

7280
### Tools Implemented
81+
7382
- `calculate`: Core arithmetic with progress notifications
7483
- `demo_progress`: Progress notification demonstration
7584
- `solve_math_problem`: Stub tool (shows graceful degradation)
7685
- `explain_formula`: Stub tool
7786
- `calculator_assistant`: Stub tool
7887

7988
### Resources Available
89+
8090
- `calculator://constants`: Math constants (pi, e)
8191
- `calculator://stats`: Process uptime metrics
8292
- `calculator://history/*`: Always returns 404 (stateless limitation)
8393
- `formulas://library`: Mathematical formula collection
8494
- `request://current`: Current request metadata
8595

8696
### Prompts Defined
97+
8798
- `explain-calculation`: Step-by-step calculation explanations
8899
- `generate-problems`: Practice problem generation
89100
- `calculator-tutor`: Interactive tutoring sessions
90101

91102
## Common Development Commands
92103

93104
### Essential Workflow
105+
94106
```bash
95107
# Install dependencies
96108
npm install
@@ -109,6 +121,7 @@ npm run ci
109121
```
110122

111123
### Testing & Validation
124+
112125
```bash
113126
# Health checks
114127
curl http://localhost:1071/health
@@ -127,6 +140,7 @@ npx @modelcontextprotocol/inspector --cli http://localhost:1071/mcp
127140
```
128141

129142
### Code Quality
143+
130144
```bash
131145
npm run lint # ESLint checks
132146
npm run lint:fix # Auto-fix linting issues
@@ -138,12 +152,14 @@ npm run format:check # Check formatting without changes
138152
## Critical Configuration Notes
139153

140154
### TypeScript Settings
155+
141156
- Uses `"moduleResolution": "bundler"` (not "node")
142157
- Package.json has `"type": "module"`
143158
- Outputs ES modules to `dist/` with source maps and declarations
144159
- Strict TypeScript configuration enabled
145160

146161
### Environment Variables
162+
147163
```bash
148164
PORT=1071 # Server port
149165
CORS_ORIGIN="*" # CORS policy (restrict in production)
@@ -154,6 +170,7 @@ NODE_ENV=production # Production optimizations
154170
```
155171

156172
### Security Requirements
173+
157174
- DNS rebinding protection always enabled
158175
- Rate limiting on `/mcp` endpoint
159176
- Stack traces hidden in production
@@ -163,6 +180,7 @@ NODE_ENV=production # Production optimizations
163180
## Educational Patterns to Follow
164181

165182
### Adding New Tools
183+
166184
1. Define schema in `types.ts` schemas object (compiled once at startup)
167185
2. Use Zod for parameter validation with `.describe()` for documentation
168186
3. Generate unique `requestId` for correlation
@@ -171,28 +189,27 @@ NODE_ENV=production # Production optimizations
171189
6. Use `SchemaInput<'toolName'>` type for type-safe parameter handling
172190

173191
### Error Handling Best Practices
192+
174193
```typescript
175194
// Use protocol-compliant McpError for predictable failures
176195
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
177196

178-
throw new McpError(
179-
ErrorCode.InvalidParams,
180-
'Division by zero is not allowed.'
181-
);
197+
throw new McpError(ErrorCode.InvalidParams, 'Division by zero is not allowed.');
182198

183199
// Global error handler catches unexpected errors
184200
requestLogger.error('Unhandled error in MCP request handler', { error });
185201
res.status(500).json({
186202
jsonrpc: '2.0',
187203
error: {
188204
code: ErrorCode.InternalError,
189-
message: 'An internal server error occurred.'
205+
message: 'An internal server error occurred.',
190206
},
191-
id: req.body?.id || null
207+
id: req.body?.id || null,
192208
});
193209
```
194210

195211
### Request Lifecycle Pattern
212+
196213
1. Generate unique `requestId` for correlation
197214
2. Create contextual logger with `requestId`
198215
3. Create fresh MCP server and transport instances
@@ -203,13 +220,15 @@ res.status(500).json({
203220
## Testing Stateless Behavior
204221

205222
### Verification Points
223+
206224
- Each request creates new server instance
207225
- No shared state between concurrent requests
208226
- Request correlation works via `requestId`
209227
- Cleanup happens properly on connection close
210228
- Metrics collection doesn't leak memory
211229

212230
### Common Issues to Watch
231+
213232
- Forgetting to set `sessionIdGenerator: undefined`
214233
- Missing cleanup in `res.on('close')` listener
215234
- Sharing state accidentally via closures
@@ -218,19 +237,22 @@ res.status(500).json({
218237
## Production Deployment
219238

220239
### Containerization
240+
221241
- Multi-stage Dockerfile (builder + production stages)
222242
- Docker Compose with health checks
223243
- Minimal production image (no dev dependencies)
224244

225245
### Serverless Ready
246+
226247
- `handleMCPRequest` function can be exported as serverless handler
227248
- No persistent state to manage
228249
- Scales infinitely without coordination
229250

230251
### Monitoring
252+
231253
- Structured JSON logging with correlation
232254
- Prometheus-style metrics endpoint
233255
- Health checks for load balancers
234256
- Request duration and tool execution histograms
235257

236-
This server demonstrates that stateless architecture enables simpler, more secure, and infinitely scalable MCP implementations. The educational approach teaches both what to build and what NOT to build, making it an invaluable learning resource.
258+
This server demonstrates that stateless architecture enables simpler, more secure, and infinitely scalable MCP implementations. The educational approach teaches both what to build and what NOT to build, making it an invaluable learning resource.

0 commit comments

Comments
 (0)