11 KiB
Common Errors in TypeScript MCP Servers
This document details 10+ production issues that occur when building MCP servers with TypeScript on Cloudflare Workers, along with their solutions.
Error 1: Export Syntax Issues (CRITICAL)
Error Message:
Cannot read properties of undefined (reading 'map')
Source: honojs/hono#3955, honojs/vite-plugins#237
Why It Happens:
Vite + Cloudflare Workers require direct export of the Hono app, not an object wrapper. The object wrapper { fetch: app.fetch } causes cryptic build errors because Vite's module resolution fails to properly handle the wrapped export.
Prevention:
// ❌ WRONG - Causes build errors
export default { fetch: app.fetch };
// ✅ CORRECT - Direct export
export default app;
// Also works for Module Worker format:
export default {
fetch: app.fetch,
scheduled: async (event, env, ctx) => { /* cron handler */ }
};
// But ONLY if you need scheduled/queue/DO handlers
How to Fix:
- Search your codebase for
export default { fetch: - Replace with direct export:
export default app; - Rebuild:
npm run build
Error 2: Unclosed Transport Connections
Error Symptoms:
- Memory leaks in production
- Hanging connections
- Gradual performance degradation
Source: Best practice from MCP SDK maintainers
Why It Happens:
StreamableHTTPServerTransport maintains an open connection. If not explicitly closed when the HTTP response ends, the connection remains open, consuming memory.
Prevention:
app.post('/mcp', async (c) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
// CRITICAL: Always close on response end
c.res.raw.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(c.req.raw, c.res.raw, await c.req.json());
return c.body(null);
});
How to Fix:
- Add
c.res.raw.on('close', () => transport.close());after creating transport - Redeploy
Error 3: Tool Schema Validation Failure
Error Message:
ListTools request handler fails to generate inputSchema
Source: GitHub modelcontextprotocol/typescript-sdk#1028
Why It Happens: Zod schemas need to be converted to JSON Schema for MCP protocol compliance. The SDK handles this automatically, but errors occur if schemas are malformed or if manual conversion is attempted incorrectly.
Prevention:
// ✅ CORRECT - SDK handles Zod → JSON Schema conversion
server.registerTool(
'tool-name',
{
description: 'Tool description',
inputSchema: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number')
})
},
async ({ a, b }) => {
return { content: [{ type: 'text', text: String(a + b) }] };
}
);
// ❌ WRONG - Manual conversion not needed
import { zodToJsonSchema } from 'zod-to-json-schema';
server.registerTool('tool-name', {
inputSchema: zodToJsonSchema(schema) // Don't do this
}, handler);
How to Fix:
- Ensure using SDK v1.20.2+
- Pass Zod schema directly to
inputSchema - Do NOT manually convert with
zodToJsonSchema()unless absolutely necessary
Error 4: Tool Arguments Not Passed to Handler
Error Symptoms:
- Handler receives
undefinedfor all arguments - Tool execution fails with "cannot read property"
Source: GitHub modelcontextprotocol/typescript-sdk#1026
Why It Happens: Type mismatch between schema definition and handler signature. The SDK expects exact type alignment.
Prevention:
// Define schema
const schema = z.object({
a: z.number(),
b: z.number()
});
// Infer type from schema
type Input = z.infer<typeof schema>;
// Use typed handler
server.registerTool(
'add',
{ description: 'Adds numbers', inputSchema: schema },
async (args: Input) => { // Type must match schema
// args.a and args.b are properly typed and passed
return {
content: [{ type: 'text', text: String(args.a + args.b) }]
};
}
);
How to Fix:
- Use
z.infer<typeof schema>to get the type - Type the handler parameter explicitly
- Ensure parameter names match schema keys exactly
Error 5: CORS Misconfiguration
Error Symptoms:
- Browser clients can't connect
- "No 'Access-Control-Allow-Origin' header" error
- Preflight (OPTIONS) requests fail
Source: Common production issue
Why It Happens: MCP servers accessed from browsers need CORS headers. Cloudflare Workers don't add these by default.
Prevention:
import { cors } from 'hono/cors';
app.use('/mcp', cors({
origin: [
'http://localhost:3000', // Dev
'https://your-app.com' // Prod
],
allowMethods: ['POST', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization'],
credentials: true // If using cookies/auth
}));
app.post('/mcp', async (c) => {
// MCP handler
});
How to Fix:
- Install:
npm install hono - Add CORS middleware before MCP endpoint
- Update
originarray with your domains - Redeploy
Error 6: Missing Rate Limiting
Error Symptoms:
- API abuse
- DDoS vulnerability
- Unexpected high costs
Source: Production security best practice
Why It Happens: Public MCP endpoints without rate limiting are vulnerable to abuse.
Prevention:
app.post('/mcp', async (c) => {
// Rate limit by IP
const ip = c.req.header('CF-Connecting-IP') || 'unknown';
const rateLimitKey = `ratelimit:${ip}:${Math.floor(Date.now() / 60000)}`;
const count = await c.env.CACHE.get(rateLimitKey);
if (count && parseInt(count) >= 100) { // 100 req/min
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await c.env.CACHE.put(
rateLimitKey,
String((parseInt(count || '0') + 1)),
{ expirationTtl: 60 }
);
// Continue with MCP handler
});
How to Fix:
- Add KV namespace for rate limiting
- Implement IP-based rate limiting
- Adjust limits based on your needs
- Consider using Cloudflare Rate Limiting (paid feature)
Error 7: TypeScript Compilation Memory Issues
Error Message:
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Source: GitHub modelcontextprotocol/typescript-sdk#985
Why It Happens: The MCP SDK has a large dependency tree. TypeScript compilation can exceed default Node.js memory limits (512MB).
Prevention:
// package.json
{
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=4096' tsc && vite build",
"typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --noEmit"
}
}
How to Fix:
- Update build scripts with NODE_OPTIONS
- Increase to 4096MB (4GB)
- If still failing, try 8192MB
- Consider using
tsupinstead of rawtscfor faster builds
Error 8: UriTemplate ReDoS Vulnerability
Error Symptoms:
- Server hangs on certain URI patterns
- CPU maxed out
- Timeouts
Source: GitHub modelcontextprotocol/typescript-sdk#965 (Security)
Why It Happens: Regex denial-of-service (ReDoS) in URI template parsing. Malicious URIs with nested patterns cause exponential regex evaluation.
Prevention: Update to SDK v1.20.2 or later (includes fix):
npm install @modelcontextprotocol/sdk@latest
How to Fix:
- Check current version:
npm list @modelcontextprotocol/sdk - If < 1.20.2, update:
npm install @modelcontextprotocol/sdk@latest - Rebuild and redeploy
Error 9: Authentication Bypass
Error Symptoms:
- Unauthorized access to tools
- API abuse
- Data leaks
Source: Production security best practice
Why It Happens: MCP servers deployed without authentication are publicly accessible.
Prevention:
// Use API key authentication
app.use('/mcp', async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return c.json({ error: 'Unauthorized' }, 401);
}
const apiKey = authHeader.replace('Bearer ', '');
const isValid = await c.env.MCP_API_KEYS.get(`key:${apiKey}`);
if (!isValid) {
return c.json({ error: 'Invalid API key' }, 403);
}
await next();
});
How to Fix:
- Add authentication middleware (see
authenticated-server.tstemplate) - Create KV namespace for API keys
- Generate secure API keys
- Distribute keys securely to authorized clients
Error 10: Environment Variable Leakage
Error Symptoms:
- Secrets exposed in logs
- API keys visible in error messages
- Security breach
Source: Cloudflare Workers security best practice
Why It Happens:
Logging or returning env objects exposes all secrets.
Prevention:
// ❌ WRONG - Exposes ALL secrets
console.log('Env:', JSON.stringify(env));
return c.json({ env }, 500);
// ✅ CORRECT - Never log env directly
try {
const apiKey = env.MY_API_KEY; // Use specific keys only
// ... use apiKey
} catch (error) {
console.error('Operation failed:', error.message); // Safe
return c.json({ error: 'Internal error' }, 500);
}
How to Fix:
- Search codebase for
console.log(envorJSON.stringify(env) - Remove all env logging
- Use specific environment variables only
- Never return env in responses
Additional Common Issues
Issue 11: Missing Zod Descriptions
Why It Matters: LLMs use parameter descriptions to understand tools. Missing descriptions = poor tool usage.
Fix:
// ❌ BAD - No descriptions
z.object({ name: z.string(), age: z.number() })
// ✅ GOOD - Clear descriptions
z.object({
name: z.string().describe('User full name'),
age: z.number().describe('User age in years')
})
Issue 12: Large Response Payloads
Problem: Returning huge JSON objects or binary data causes timeouts.
Fix:
// Limit response size
const MAX_RESPONSE_SIZE = 100000; // 100KB
server.registerTool('fetch-data', { ... }, async ({ url }) => {
const response = await fetch(url);
const text = await response.text();
if (text.length > MAX_RESPONSE_SIZE) {
return {
content: [{
type: 'text',
text: `Response too large (${text.length} bytes). First 100KB:\n${text.slice(0, MAX_RESPONSE_SIZE)}`
}]
};
}
return { content: [{ type: 'text', text }] };
});
Issue 13: Not Handling Async Errors
Problem: Unhandled promise rejections crash Workers.
Fix:
server.registerTool('risky-operation', { ... }, async (args) => {
try {
const result = await riskyAsyncOperation(args);
return { content: [{ type: 'text', text: result }] };
} catch (error) {
return {
content: [{
type: 'text',
text: `Operation failed: ${(error as Error).message}`
}],
isError: true
};
}
});
Debugging Checklist
When encountering MCP server issues:
- Check SDK version (
npm list @modelcontextprotocol/sdk) - Verify export syntax (direct export, not object wrapper)
- Confirm transport is closed on response end
- Test with MCP Inspector (
npx @modelcontextprotocol/inspector) - Check Cloudflare Workers logs (
wrangler tail) - Verify authentication middleware (if production)
- Test CORS headers (if browser clients)
- Review Zod schemas for proper types
- Check rate limiting implementation
- Ensure no env variables are logged
Last Updated: 2025-10-28 Verified Against: @modelcontextprotocol/sdk@1.20.2