From 5274b748bab5538f26fc493e64c37742408d63da Mon Sep 17 00:00:00 2001 From: Nikita Nemirovsky Date: Sun, 3 May 2026 19:40:02 +0800 Subject: [PATCH 1/3] fix(mcp): sanitize tool schemas for codex --- cmd/iwdp-mcp/main.go | 67 +++++++++++++++++++++++++++++++++------- cmd/iwdp-mcp/mcp_test.go | 29 ++++++++++++++--- 2 files changed, 80 insertions(+), 16 deletions(-) diff --git a/cmd/iwdp-mcp/main.go b/cmd/iwdp-mcp/main.go index 940b5bb..4a4b297 100644 --- a/cmd/iwdp-mcp/main.go +++ b/cmd/iwdp-mcp/main.go @@ -9,7 +9,6 @@ import ( "net/url" "os" "path/filepath" - "reflect" "strings" "sync" @@ -76,23 +75,67 @@ func main() { } func addTool[In, Out any](s *mcp.Server, t *mcp.Tool, h mcp.ToolHandlerFor[In, Out]) { - if t.InputSchema == nil && isEmptyStructType(reflect.TypeFor[In]()) { - copy := *t - copy.InputSchema = emptyObjectInputSchema() - t = © + copy := *t + if copy.InputSchema == nil { + schema, err := jsonschema.For[In](nil) + if err != nil { + panic(fmt.Sprintf("AddTool: tool %q: input schema: %v", copy.Name, err)) + } + copy.InputSchema = codexCompatibleInputSchema(copy.Name, schema) + } else { + copy.InputSchema = codexCompatibleInputSchema(copy.Name, copy.InputSchema) + } + mcp.AddTool(s, ©, h) +} + +func codexCompatibleInputSchema(toolName string, schema any) map[string]any { + data, err := json.Marshal(schema) + if err != nil { + panic(fmt.Sprintf("AddTool: tool %q: marshal input schema: %v", toolName, err)) } - mcp.AddTool(s, t, h) + var out map[string]any + if err := json.Unmarshal(data, &out); err != nil { + panic(fmt.Sprintf("AddTool: tool %q: unmarshal input schema: %v", toolName, err)) + } + normalizeSchema(out) + return out } -func isEmptyStructType(t reflect.Type) bool { - return t.Kind() == reflect.Struct && t.NumField() == 0 +func normalizeSchema(schema any) { + switch s := schema.(type) { + case map[string]any: + for _, value := range s { + normalizeSchema(value) + } + if schemaTypeIncludes(s["type"], "object") { + if _, ok := s["properties"].(map[string]any); !ok { + s["properties"] = map[string]any{} + } + } + if schemaTypeIncludes(s["type"], "array") { + if _, ok := s["items"].(map[string]any); !ok { + s["items"] = map[string]any{} + } + } + case []any: + for _, value := range s { + normalizeSchema(value) + } + } } -func emptyObjectInputSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{}, +func schemaTypeIncludes(value any, typ string) bool { + switch v := value.(type) { + case string: + return v == typ + case []any: + for _, item := range v { + if item == typ { + return true + } + } } + return false } // --- Input/Output types for MCP tools --- diff --git a/cmd/iwdp-mcp/mcp_test.go b/cmd/iwdp-mcp/mcp_test.go index f171caa..18476ea 100644 --- a/cmd/iwdp-mcp/mcp_test.go +++ b/cmd/iwdp-mcp/mcp_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" @@ -34,7 +35,7 @@ func TestServerCreationWithRegisterTools(t *testing.T) { } } -func TestToolInputSchemasIncludeProperties(t *testing.T) { +func TestToolInputSchemasAreCodexCompatible(t *testing.T) { ctx := context.Background() impl := &mcp.Implementation{Name: "iwdp-mcp-test", Version: "0.1.0"} server := mcp.NewServer(impl, nil) @@ -71,11 +72,31 @@ func TestToolInputSchemasIncludeProperties(t *testing.T) { if err := json.Unmarshal(data, &schema); err != nil { t.Fatalf("unmarshal input schema for %s: %v", tool.Name, err) } - if schema["type"] == "object" { - if _, ok := schema["properties"]; !ok { - t.Fatalf("tool %s object input schema is missing properties: %s", tool.Name, data) + assertCodexCompatibleInputSchema(t, tool.Name, schema, tool.Name) + } +} + +func assertCodexCompatibleInputSchema(t *testing.T, toolName string, schema any, path string) { + t.Helper() + switch s := schema.(type) { + case map[string]any: + if schemaTypeIncludes(s["type"], "object") { + if _, ok := s["properties"].(map[string]any); !ok { + t.Fatalf("tool %s object input schema is missing properties at %s", toolName, path) } } + if schemaTypeIncludes(s["type"], "array") { + if _, ok := s["items"].(map[string]any); !ok { + t.Fatalf("tool %s array input schema items is not an object at %s", toolName, path) + } + } + for key, value := range s { + assertCodexCompatibleInputSchema(t, toolName, value, path+"."+key) + } + case []any: + for i, value := range s { + assertCodexCompatibleInputSchema(t, toolName, value, fmt.Sprintf("%s[%d]", path, i)) + } } } From a4efe3f958bac55bbeab6b04544d3f2a416dce10 Mon Sep 17 00:00:00 2001 From: Nikita Nemirovsky Date: Sun, 3 May 2026 19:49:31 +0800 Subject: [PATCH 2/3] chore(release): bump version to 0.5.3 --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- cmd/iwdp-mcp/main.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 66d83c6..1d6b944 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,6 +1,6 @@ { "name": "iwdp-mcp", - "version": "0.5.2", + "version": "0.5.3", "description": "iOS Safari debugging via ios-webkit-debug-proxy — MCP server with full WebKit Inspector Protocol support", "owner": { "name": "nnemirovsky" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 07c1405..4766ae0 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "iwdp-mcp", - "version": "0.5.2", + "version": "0.5.3", "description": "iOS Safari debugging via ios-webkit-debug-proxy — MCP server with full WebKit Inspector Protocol support", "mcpServers": { "iwdp-mcp": { diff --git a/cmd/iwdp-mcp/main.go b/cmd/iwdp-mcp/main.go index 4a4b297..1a23181 100644 --- a/cmd/iwdp-mcp/main.go +++ b/cmd/iwdp-mcp/main.go @@ -64,7 +64,7 @@ func lookupInterceptStage(requestID string) string { func main() { server := mcp.NewServer(&mcp.Implementation{ Name: "iwdp-mcp", - Version: "0.5.2", + Version: "0.5.3", }, nil) registerTools(server) From f69d8c44368a8bcb06464529467c31e48e0d3e98 Mon Sep 17 00:00:00 2001 From: Nikita Nemirovsky Date: Sun, 3 May 2026 20:55:51 +0800 Subject: [PATCH 3/3] test(e2e): tolerate unsupported network emulation --- e2e/simulator_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/simulator_test.go b/e2e/simulator_test.go index b41b469..a1924ec 100644 --- a/e2e/simulator_test.go +++ b/e2e/simulator_test.go @@ -986,6 +986,10 @@ func TestSim_SetEmulatedConditions(t *testing.T) { _ = monitor.Start(ctx, client) if err := tools.SetEmulatedConditions(ctx, client, 100000); err != nil { + if strings.Contains(err.Error(), "'Network.setEmulatedConditions' was not found") { + t.Log("Network.setEmulatedConditions is not available in this WebKit/iwdp environment") + return + } t.Fatalf("SetEmulatedConditions: %v", err) } // Reset