From 07300e84530b22d5d60b8d86655edf20cceda6e8 Mon Sep 17 00:00:00 2001 From: Jerome Swannack Date: Fri, 5 Dec 2025 13:35:29 +0000 Subject: [PATCH] Add dev scripts for mitmproxy debugging and fix JSON-RPC error responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dev-with-mitm.sh for TUI-based traffic inspection - Add dev-with-mitmweb.sh for web UI-based traffic inspection - Fix missing 'id' field in JSON-RPC error responses (required by spec) - Migrate from zod-to-json-schema to Zod v4's native z.toJSONSchema() The mitmproxy scripts help developers debug MCP traffic by running the server through a reverse proxy that intercepts all client requests. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- dev-with-mitm.sh | 31 +++++++++++++++++++++++++++++++ dev-with-mitmweb.sh | 31 +++++++++++++++++++++++++++++++ src/modules/mcp/handlers/shttp.ts | 4 ++++ src/modules/mcp/services/mcp.ts | 30 +++++++++++++++++------------- 4 files changed, 83 insertions(+), 13 deletions(-) create mode 100755 dev-with-mitm.sh create mode 100755 dev-with-mitmweb.sh diff --git a/dev-with-mitm.sh b/dev-with-mitm.sh new file mode 100755 index 0000000..fe8783d --- /dev/null +++ b/dev-with-mitm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Easy script to run MCP server with mitmproxy intercepting client traffic + +echo "🚀 Starting MCP server with mitmproxy..." +echo "" +echo "This will:" +echo " 1. Start your MCP server on port 3232" +echo " 2. Start mitmproxy reverse proxy on port 8080" +echo " 3. Intercept all client → server traffic" +echo "" +echo "📡 Connect your MCP client to: http://localhost:8080/mcp" +echo "🔍 View traffic in mitmproxy TUI" +echo "" +echo "Press Ctrl+C to stop both processes" +echo "" + +# Trap Ctrl+C to kill both processes +trap 'kill 0' EXIT + +# Start the MCP server in background +npm run dev & +SERVER_PID=$! + +# Give server time to start +sleep 3 + +# Start mitmproxy in reverse proxy mode (foreground so we see the TUI) +mitmproxy --mode reverse:http://localhost:3232 --listen-port 8080 + +# This line won't be reached until mitmproxy exits +wait diff --git a/dev-with-mitmweb.sh b/dev-with-mitmweb.sh new file mode 100755 index 0000000..128d1e7 --- /dev/null +++ b/dev-with-mitmweb.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Easy script to run MCP server with mitmproxy web interface + +echo "🚀 Starting MCP server with mitmproxy web UI..." +echo "" +echo "This will:" +echo " 1. Start your MCP server on port 3232" +echo " 2. Start mitmproxy with web UI on port 8081" +echo " 3. Proxy client traffic through port 8080" +echo "" +echo "📡 Connect your MCP client to: http://localhost:8080/mcp" +echo "🌐 View traffic in browser at: http://localhost:8081" +echo "" +echo "Press Ctrl+C to stop both processes" +echo "" + +# Trap Ctrl+C to kill both processes +trap 'kill 0' EXIT + +# Start the MCP server in background +npm run dev & +SERVER_PID=$! + +# Give server time to start +sleep 3 + +# Start mitmweb in reverse proxy mode +mitmweb --mode reverse:http://localhost:3232 --listen-port 8080 --web-port 8081 + +# This line won't be reached until mitmweb exits +wait diff --git a/src/modules/mcp/handlers/shttp.ts b/src/modules/mcp/handlers/shttp.ts index cea0dfb..a7a58e2 100644 --- a/src/modules/mcp/handlers/shttp.ts +++ b/src/modules/mcp/handlers/shttp.ts @@ -67,6 +67,7 @@ export async function handleStreamableHTTP(req: Request, res: Response) { }); res.status(401).json({ "jsonrpc": "2.0", + "id": null, "error": { "code": -32002, "message": "User ID required" @@ -87,6 +88,7 @@ export async function handleStreamableHTTP(req: Request, res: Response) { }); res.status(401).json({ "jsonrpc": "2.0", + "id": null, "error": { "code": -32001, "message": "Session not found or access denied" @@ -148,6 +150,7 @@ export async function handleStreamableHTTP(req: Request, res: Response) { }); res.status(400).json({ "jsonrpc": "2.0", + "id": null, "error": { "code": -32600, "message": "Invalid request method for existing session" @@ -167,6 +170,7 @@ export async function handleStreamableHTTP(req: Request, res: Response) { if (!res.headersSent) { res.status(500).json({ "jsonrpc": "2.0", + "id": null, "error": { "code": -32603, "message": "Internal error during request processing" diff --git a/src/modules/mcp/services/mcp.ts b/src/modules/mcp/services/mcp.ts index 8e77c64..5c76f9d 100644 --- a/src/modules/mcp/services/mcp.ts +++ b/src/modules/mcp/services/mcp.ts @@ -19,12 +19,13 @@ import { UnsubscribeRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; -import { zodToJsonSchema } from "zod-to-json-schema"; -type ToolInput = { - type: "object"; - properties?: Record; - required?: string[]; +type ToolInput = Tool["inputSchema"]; + +// Helper to convert Zod schema to JSON schema using Zod v4's native support +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const toJsonSchema = (schema: z.ZodType): ToolInput => { + return z.toJSONSchema(schema) as ToolInput; }; /* Input schemas for tools implemented in this server */ @@ -413,40 +414,40 @@ export const createMcpServer = (): McpServerWrapper => { { name: ToolName.ECHO, description: "Echoes back the input", - inputSchema: zodToJsonSchema(EchoSchema) as ToolInput, + inputSchema: toJsonSchema(EchoSchema), }, { name: ToolName.ADD, description: "Adds two numbers", - inputSchema: zodToJsonSchema(AddSchema) as ToolInput, + inputSchema: toJsonSchema(AddSchema), }, { name: ToolName.LONG_RUNNING_OPERATION, description: "Demonstrates a long running operation with progress updates", - inputSchema: zodToJsonSchema(LongRunningOperationSchema) as ToolInput, + inputSchema: toJsonSchema(LongRunningOperationSchema), }, { name: ToolName.SAMPLE_LLM, description: "Samples from an LLM using MCP's sampling feature", - inputSchema: zodToJsonSchema(SampleLLMSchema) as ToolInput, + inputSchema: toJsonSchema(SampleLLMSchema), }, { name: ToolName.GET_TINY_IMAGE, description: "Returns the MCP_TINY_IMAGE", - inputSchema: zodToJsonSchema(GetTinyImageSchema) as ToolInput, + inputSchema: toJsonSchema(GetTinyImageSchema), }, { name: ToolName.ANNOTATED_MESSAGE, description: "Demonstrates how annotations can be used to provide metadata about content", - inputSchema: zodToJsonSchema(AnnotatedMessageSchema) as ToolInput, + inputSchema: toJsonSchema(AnnotatedMessageSchema), }, { name: ToolName.GET_RESOURCE_REFERENCE, description: "Returns a resource reference that can be used by MCP clients", - inputSchema: zodToJsonSchema(GetResourceReferenceSchema) as ToolInput, + inputSchema: toJsonSchema(GetResourceReferenceSchema), }, { name: ToolName.ELICIT_INPUTS, @@ -524,9 +525,12 @@ export const createMcpServer = (): McpServerWrapper => { ToolName.SAMPLE_LLM, maxTokens ); + const contentArray = Array.isArray(result.content) ? result.content : [result.content]; + const firstContent = contentArray[0]; + const textContent = firstContent && "text" in firstContent ? firstContent.text : JSON.stringify(result.content); return { content: [ - { type: "text", text: `LLM sampling result: ${result.content.text}` }, + { type: "text", text: `LLM sampling result: ${textContent}` }, ], }; }