In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
After struggling with Vercel's official mcp-handler library for hours, encountering mysterious 406 errors, validation bugs, and unexplained failures, we abandoned the library and built our own JSON-RPC handler. It works perfectly. This guide documents the problem, our debugging journey, and the complete solution.
The Problem: Why mcp-handler Failed
Initial Setup
We started with the "official" Vercel solution: mcp-handler v1.0.4. Following the README examples, we integrated it into our Next.js 15 app with OAuth2 authentication.
Uses actual CallToolResult and TextContent types from MCP SDK
Proper response format with type: 'tool.result'
Detailed logging throughout tool execution
Proper error handling with isError: true flag
Type-safe implementation
Step 4: Testing Locally
bash
# Clear cache and restartrm -rf .next
pnpm dev
# Test initialize
curl -X POST http://localhost:80/api/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'# Response: 200 OK with server info ✅# Test tools/list
curl -X POST http://localhost:80/api/mcp \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}'# Response: 200 OK with all 9 tools ✅
Step 5: Deploy to Vercel
bash
git add -A
git commit -m "feat: Custom JSON-RPC MCP server implementation
Replaced mcp-handler with bespoke JSON-RPC handler to bypass validation issues.
Implements initialize, tools/list, and tools/call methods with full OAuth2 auth.
Generated with Claude Code"
git push
Why This Works
1. No Arbitrary Validation
Our handler simply:
Checks for a token
Verifies it with Redis
Parses the JSON-RPC request
Routes to the appropriate handler
Returns a response
No hidden header checks. No hardcoded requirements.
2. Transparent Protocol Implementation
The MCP protocol is just JSON-RPC. We follow the spec:
method: What operation to perform
params: Arguments
id: Request identifier
result or error: Response
That's it. No abstraction layer to hide bugs.
3. Easy to Debug
Every step is visible:
code
[MCP Route] Request received for transport: mcp
[MCP Auth] Authorization header: Bearer ...
[MCP Auth] Token verified successfully for client: ...
[MCP Route] Auth successful, handling JSON-RPC request
[MCP Route] Received JSON-RPC method: tools/list
vs. mcp-handler's black box that just returns 406.
4. Easy to Extend
Adding a new tool? Add a new else if:
typescript
elseif (toolName === 'my-new-tool') {
result = awaitdoSomething(toolArgs)
}
Comparison: mcp-handler vs Custom Implementation
Debugging a 406 Error
With mcp-handler:
code
ERROR: Not Acceptable: Client must accept both application/json and text/event-stream
WHERE: Unknown (compiled code)
WHY: Hidden validation
FIX: ???
With custom handler:
code
request.json() →
check headers →
verify auth →
route to handler →
return response
↓
Everything visible, easy to debug
Conclusion
mcp-handler is broken. The 406 validation error is a fundamental bug in the library that can't be worked around. Rather than spend more time fighting it, we built our own JSON-RPC handler in ~360 lines of clear, debuggable code.
The result works perfectly with Claude, ChatGPT, and any MCP client. It's faster, clearer, and maintainable.
Sometimes the best solution is to bypass the abstraction and implement the protocol yourself.
Final Implementation Status
Production Ready
The MCP server is fully functional and deployed on Vercel.
All 9 tools verified working:
get-tenants - List all available tenants
get-business-info - Retrieve business information
get-ecommerce-products - Get SKU-based products
get-content-products - Get service/solution products
get-faqs - Retrieve FAQ items
get-solutions - Get available solutions
get-pages - Get static pages
get-collections - Get product categories
search-similar-content - Vector similarity search
Verified with:
Claude (official MCP client)
ChatGPT (official MCP client)
Local testing via curl
OAuth2 authentication flow
Key Lessons from Implementation
Don't use third-party abstractions when the protocol is simple
mcp-handler added complexity and bugs
Direct JSON-RPC is ~360 lines of clear code
Better for debugging and customization
Use proper MCP SDK types
Import CallToolResult and TextContent from SDK
Don't hardcode response structures
Ensures spec compliance
Watch for cached database functions in serverless
Functions using unstable_cache fail in edge context