Skip to content

Commit 91b9f18

Browse files
committed
fix: protocol compliance audit — event collectors, 8 bug fixes, bump 0.4.0
- Add event-based collectors for CPU/Script profiler, Memory, Heap, Animation tracking (data delivered via events, not method responses) - Fix Debugger.setBreakpointByUrl: condition must be inside options object - Fix DOMDebugger.setEventBreakpoint: add required breakpointType parameter - Fix Network.setEmulatedConditions: remove non-existent latencyMs parameter - Fix Network.interceptWithResponse: use "status" not "statusCode" - Fix Canvas.startRecording: use frameCount (int) not singleFrame (bool) - Remove LayerTree.getLayerContent: method doesn't exist in WebKit protocol - Fix ServiceWorker: use navigator.serviceWorker JS API (domain only works on SW-specific connections, not page connections through iwdp) - Fix Animation tracking event struct to match WebKit TrackingUpdate type - Fix Heap tracking: remove snapshot event handling (50-200MB payloads crash iwdp's WebSocket relay), only collect lightweight GC events - Version bump to 0.4.0
1 parent 4dc2eba commit 91b9f18

14 files changed

Lines changed: 959 additions & 233 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "iwdp-mcp",
3-
"version": "0.3.5",
3+
"version": "0.4.0",
44
"description": "iOS Safari debugging via ios-webkit-debug-proxy — MCP server with full WebKit Inspector Protocol support",
55
"owner": {
66
"name": "nnemirovsky"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "iwdp-mcp",
3-
"version": "0.3.5",
3+
"version": "0.4.0",
44
"description": "iOS Safari debugging via ios-webkit-debug-proxy — MCP server with full WebKit Inspector Protocol support",
55
"mcpServers": {
66
"iwdp-mcp": {

cmd/iwdp-mcp/main.go

Lines changed: 138 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ import (
2020

2121
// session holds the active WebKit client and collectors.
2222
type session struct {
23-
mu sync.Mutex
24-
client *webkit.Client
25-
networkMonitor *tools.NetworkMonitor
26-
consoleCollector *tools.ConsoleCollector
27-
timelineCollector *tools.TimelineCollector
28-
interceptionCollector *tools.InterceptionCollector
23+
mu sync.Mutex
24+
client *webkit.Client
25+
networkMonitor *tools.NetworkMonitor
26+
consoleCollector *tools.ConsoleCollector
27+
timelineCollector *tools.TimelineCollector
28+
interceptionCollector *tools.InterceptionCollector
29+
cpuProfilerCollector *tools.CPUProfilerCollector
30+
scriptProfilerCollector *tools.ScriptProfilerCollector
31+
memoryTrackingCollector *tools.MemoryTrackingCollector
32+
heapTrackingCollector *tools.HeapTrackingCollector
33+
animationTrackingCollector *tools.AnimationTrackingCollector
2934
}
3035

3136
var sess session
@@ -58,7 +63,7 @@ func lookupInterceptStage(requestID string) string {
5863
func main() {
5964
server := mcp.NewServer(&mcp.Implementation{
6065
Name: "iwdp-mcp",
61-
Version: "0.3.5",
66+
Version: "0.4.0",
6267
}, nil)
6368

6469
registerTools(server)
@@ -236,8 +241,7 @@ type InterceptWithResponseInput struct {
236241
}
237242

238243
type SetEmulatedConditionsInput struct {
239-
BytesPerSecondLimit int `json:"bytes_per_second_limit" jsonschema:"bytes per second limit"`
240-
LatencyMs float64 `json:"latency_ms" jsonschema:"latency in milliseconds"`
244+
BytesPerSecondLimit int `json:"bytes_per_second_limit" jsonschema:"bytes per second limit"`
241245
}
242246

243247
type SetResourceCachingDisabledInput struct {
@@ -330,7 +334,8 @@ type DOMBreakpointInput struct {
330334
}
331335

332336
type EventBreakpointInput struct {
333-
EventName string `json:"event_name"`
337+
BreakpointType string `json:"breakpoint_type" jsonschema:"required,breakpoint type: animation-frame, interval, listener, or timeout"`
338+
EventName string `json:"event_name"`
334339
}
335340

336341
type URLBreakpointInput struct {
@@ -358,8 +363,8 @@ type AnimationIDInput struct {
358363
}
359364

360365
type CanvasIDInput struct {
361-
CanvasID string `json:"canvas_id"`
362-
SingleFrame bool `json:"single_frame,omitempty"`
366+
CanvasID string `json:"canvas_id"`
367+
FrameCount int `json:"frame_count,omitempty" jsonschema:"number of frames to record (0 for unlimited)"`
363368
}
364369

365370
type ShaderSourceInput struct {
@@ -1126,7 +1131,7 @@ func registerTools(server *mcp.Server) {
11261131
if err != nil {
11271132
return nil, OKOutput{}, err
11281133
}
1129-
return nil, ok(), tools.SetEmulatedConditions(ctx, c, input.BytesPerSecondLimit, input.LatencyMs)
1134+
return nil, ok(), tools.SetEmulatedConditions(ctx, c, input.BytesPerSecondLimit)
11301135
})
11311136

11321137
mcp.AddTool(server, &mcp.Tool{
@@ -1568,7 +1573,7 @@ func registerTools(server *mcp.Server) {
15681573
if err != nil {
15691574
return nil, OKOutput{}, err
15701575
}
1571-
return nil, ok(), tools.SetEventBreakpoint(ctx, c, input.EventName)
1576+
return nil, ok(), tools.SetEventBreakpoint(ctx, c, input.BreakpointType, input.EventName)
15721577
})
15731578

15741579
mcp.AddTool(server, &mcp.Tool{
@@ -1578,7 +1583,7 @@ func registerTools(server *mcp.Server) {
15781583
if err != nil {
15791584
return nil, OKOutput{}, err
15801585
}
1581-
return nil, ok(), tools.RemoveEventBreakpoint(ctx, c, input.EventName)
1586+
return nil, ok(), tools.RemoveEventBreakpoint(ctx, c, input.BreakpointType, input.EventName)
15821587
})
15831588

15841589
mcp.AddTool(server, &mcp.Tool{
@@ -1652,23 +1657,43 @@ func registerTools(server *mcp.Server) {
16521657

16531658
// --- Memory & Heap ---
16541659
mcp.AddTool(server, &mcp.Tool{
1655-
Name: "memory_start_tracking", Description: "Start tracking memory usage",
1660+
Name: "memory_start_tracking", Description: "Start tracking memory usage — collects memory category events",
16561661
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
16571662
c, err := getClient(ctx)
16581663
if err != nil {
16591664
return nil, OKOutput{}, err
16601665
}
1661-
return nil, ok(), tools.MemoryStartTracking(ctx, c)
1666+
sess.mu.Lock()
1667+
if sess.memoryTrackingCollector == nil {
1668+
sess.memoryTrackingCollector = tools.NewMemoryTrackingCollector()
1669+
}
1670+
collector := sess.memoryTrackingCollector
1671+
sess.mu.Unlock()
1672+
return nil, ok(), collector.Start(ctx, c)
16621673
})
16631674

16641675
mcp.AddTool(server, &mcp.Tool{
1665-
Name: "memory_stop_tracking", Description: "Stop tracking memory usage",
1676+
Name: "memory_stop_tracking", Description: "Stop tracking memory usage and get collected memory events",
16661677
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
16671678
c, err := getClient(ctx)
16681679
if err != nil {
1669-
return nil, OKOutput{}, err
1680+
return nil, RawOutput{}, err
1681+
}
1682+
sess.mu.Lock()
1683+
collector := sess.memoryTrackingCollector
1684+
sess.mu.Unlock()
1685+
if collector == nil {
1686+
return nil, RawOutput{}, fmt.Errorf("memory tracking not started — use memory_start_tracking first")
16701687
}
1671-
return nil, ok(), tools.MemoryStopTracking(ctx, c)
1688+
result, err := collector.Stop(ctx, c)
1689+
if err != nil {
1690+
return nil, RawOutput{}, err
1691+
}
1692+
if fileResult, ferr := largeResultToFile(result, "memory-tracking"); fileResult != nil {
1693+
return fileResult, nil, ferr
1694+
}
1695+
data, _ := json.Marshal(result)
1696+
return nil, RawOutput{Result: data}, nil
16721697
})
16731698

16741699
mcp.AddTool(server, &mcp.Tool{
@@ -1690,23 +1715,43 @@ func registerTools(server *mcp.Server) {
16901715
})
16911716

16921717
mcp.AddTool(server, &mcp.Tool{
1693-
Name: "heap_start_tracking", Description: "Start tracking heap allocations",
1718+
Name: "heap_start_tracking", Description: "Start tracking heap allocations — collects heap snapshot events",
16941719
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
16951720
c, err := getClient(ctx)
16961721
if err != nil {
16971722
return nil, OKOutput{}, err
16981723
}
1699-
return nil, ok(), tools.HeapStartTracking(ctx, c)
1724+
sess.mu.Lock()
1725+
if sess.heapTrackingCollector == nil {
1726+
sess.heapTrackingCollector = tools.NewHeapTrackingCollector()
1727+
}
1728+
collector := sess.heapTrackingCollector
1729+
sess.mu.Unlock()
1730+
return nil, ok(), collector.Start(ctx, c)
17001731
})
17011732

17021733
mcp.AddTool(server, &mcp.Tool{
1703-
Name: "heap_stop_tracking", Description: "Stop tracking heap allocations",
1734+
Name: "heap_stop_tracking", Description: "Stop tracking heap allocations and get collected heap events",
17041735
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17051736
c, err := getClient(ctx)
17061737
if err != nil {
1707-
return nil, OKOutput{}, err
1738+
return nil, RawOutput{}, err
1739+
}
1740+
sess.mu.Lock()
1741+
collector := sess.heapTrackingCollector
1742+
sess.mu.Unlock()
1743+
if collector == nil {
1744+
return nil, RawOutput{}, fmt.Errorf("heap tracking not started — use heap_start_tracking first")
1745+
}
1746+
result, err := collector.Stop(ctx, c)
1747+
if err != nil {
1748+
return nil, RawOutput{}, err
1749+
}
1750+
if fileResult, ferr := largeResultToFile(result, "heap-tracking"); fileResult != nil {
1751+
return fileResult, nil, ferr
17081752
}
1709-
return nil, ok(), tools.HeapStopTracking(ctx, c)
1753+
data, _ := json.Marshal(result)
1754+
return nil, RawOutput{Result: data}, nil
17101755
})
17111756

17121757
mcp.AddTool(server, &mcp.Tool{
@@ -1721,51 +1766,83 @@ func registerTools(server *mcp.Server) {
17211766

17221767
// --- Profiler ---
17231768
mcp.AddTool(server, &mcp.Tool{
1724-
Name: "cpu_start_profiling", Description: "Start CPU profiling",
1769+
Name: "cpu_start_profiling", Description: "Start CPU profiling — collects CPU usage samples via events",
17251770
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17261771
c, err := getClient(ctx)
17271772
if err != nil {
17281773
return nil, OKOutput{}, err
17291774
}
1730-
return nil, ok(), tools.CPUStartProfiling(ctx, c)
1775+
sess.mu.Lock()
1776+
if sess.cpuProfilerCollector == nil {
1777+
sess.cpuProfilerCollector = tools.NewCPUProfilerCollector()
1778+
}
1779+
collector := sess.cpuProfilerCollector
1780+
sess.mu.Unlock()
1781+
return nil, ok(), collector.Start(ctx, c)
17311782
})
17321783

17331784
mcp.AddTool(server, &mcp.Tool{
1734-
Name: "cpu_stop_profiling", Description: "Stop CPU profiling and get results",
1785+
Name: "cpu_stop_profiling", Description: "Stop CPU profiling and get collected CPU usage events",
17351786
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17361787
c, err := getClient(ctx)
17371788
if err != nil {
17381789
return nil, RawOutput{}, err
17391790
}
1740-
result, err := tools.CPUStopProfiling(ctx, c)
1791+
sess.mu.Lock()
1792+
collector := sess.cpuProfilerCollector
1793+
sess.mu.Unlock()
1794+
if collector == nil {
1795+
return nil, RawOutput{}, fmt.Errorf("CPU profiling not started — use cpu_start_profiling first")
1796+
}
1797+
result, err := collector.Stop(ctx, c)
17411798
if err != nil {
17421799
return nil, RawOutput{}, err
17431800
}
1744-
return nil, RawOutput{Result: result}, nil
1801+
if fileResult, ferr := largeResultToFile(result, "cpu-profile"); fileResult != nil {
1802+
return fileResult, nil, ferr
1803+
}
1804+
data, _ := json.Marshal(result)
1805+
return nil, RawOutput{Result: data}, nil
17451806
})
17461807

17471808
mcp.AddTool(server, &mcp.Tool{
1748-
Name: "script_start_profiling", Description: "Start script execution profiling",
1809+
Name: "script_start_profiling", Description: "Start script execution profiling with stack sampling",
17491810
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17501811
c, err := getClient(ctx)
17511812
if err != nil {
17521813
return nil, OKOutput{}, err
17531814
}
1754-
return nil, ok(), tools.ScriptStartProfiling(ctx, c)
1815+
sess.mu.Lock()
1816+
if sess.scriptProfilerCollector == nil {
1817+
sess.scriptProfilerCollector = tools.NewScriptProfilerCollector()
1818+
}
1819+
collector := sess.scriptProfilerCollector
1820+
sess.mu.Unlock()
1821+
return nil, ok(), collector.Start(ctx, c)
17551822
})
17561823

17571824
mcp.AddTool(server, &mcp.Tool{
1758-
Name: "script_stop_profiling", Description: "Stop script profiling and get results",
1825+
Name: "script_stop_profiling", Description: "Stop script profiling and get execution events + stack samples",
17591826
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17601827
c, err := getClient(ctx)
17611828
if err != nil {
17621829
return nil, RawOutput{}, err
17631830
}
1764-
result, err := tools.ScriptStopProfiling(ctx, c)
1831+
sess.mu.Lock()
1832+
collector := sess.scriptProfilerCollector
1833+
sess.mu.Unlock()
1834+
if collector == nil {
1835+
return nil, RawOutput{}, fmt.Errorf("script profiling not started — use script_start_profiling first")
1836+
}
1837+
result, err := collector.Stop(ctx, c)
17651838
if err != nil {
17661839
return nil, RawOutput{}, err
17671840
}
1768-
return nil, RawOutput{Result: result}, nil
1841+
if fileResult, ferr := largeResultToFile(result, "script-profile"); fileResult != nil {
1842+
return fileResult, nil, ferr
1843+
}
1844+
data, _ := json.Marshal(result)
1845+
return nil, RawOutput{Result: data}, nil
17691846
})
17701847

17711848
// --- Animation ---
@@ -1790,23 +1867,43 @@ func registerTools(server *mcp.Server) {
17901867
})
17911868

17921869
mcp.AddTool(server, &mcp.Tool{
1793-
Name: "animation_start_tracking", Description: "Start animation profiling",
1870+
Name: "animation_start_tracking", Description: "Start animation profiling — collects animation events",
17941871
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
17951872
c, err := getClient(ctx)
17961873
if err != nil {
17971874
return nil, OKOutput{}, err
17981875
}
1799-
return nil, ok(), tools.AnimationStartTracking(ctx, c)
1876+
sess.mu.Lock()
1877+
if sess.animationTrackingCollector == nil {
1878+
sess.animationTrackingCollector = tools.NewAnimationTrackingCollector()
1879+
}
1880+
collector := sess.animationTrackingCollector
1881+
sess.mu.Unlock()
1882+
return nil, ok(), collector.Start(ctx, c)
18001883
})
18011884

18021885
mcp.AddTool(server, &mcp.Tool{
1803-
Name: "animation_stop_tracking", Description: "Stop animation profiling",
1886+
Name: "animation_stop_tracking", Description: "Stop animation profiling and get collected animation events",
18041887
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
18051888
c, err := getClient(ctx)
18061889
if err != nil {
1807-
return nil, OKOutput{}, err
1890+
return nil, RawOutput{}, err
1891+
}
1892+
sess.mu.Lock()
1893+
collector := sess.animationTrackingCollector
1894+
sess.mu.Unlock()
1895+
if collector == nil {
1896+
return nil, RawOutput{}, fmt.Errorf("animation tracking not started — use animation_start_tracking first")
18081897
}
1809-
return nil, ok(), tools.AnimationStopTracking(ctx, c)
1898+
result, err := collector.Stop(ctx, c)
1899+
if err != nil {
1900+
return nil, RawOutput{}, err
1901+
}
1902+
if fileResult, ferr := largeResultToFile(result, "animation-tracking"); fileResult != nil {
1903+
return fileResult, nil, ferr
1904+
}
1905+
data, _ := json.Marshal(result)
1906+
return nil, RawOutput{Result: data}, nil
18101907
})
18111908

18121909
mcp.AddTool(server, &mcp.Tool{
@@ -1879,7 +1976,7 @@ func registerTools(server *mcp.Server) {
18791976
if err != nil {
18801977
return nil, OKOutput{}, err
18811978
}
1882-
return nil, ok(), tools.StartCanvasRecording(ctx, c, input.CanvasID, input.SingleFrame)
1979+
return nil, ok(), tools.StartCanvasRecording(ctx, c, input.CanvasID, input.FrameCount)
18831980
})
18841981

18851982
mcp.AddTool(server, &mcp.Tool{
@@ -1935,20 +2032,6 @@ func registerTools(server *mcp.Server) {
19352032
return nil, RawOutput{Result: result}, nil
19362033
})
19372034

1938-
mcp.AddTool(server, &mcp.Tool{
1939-
Name: "get_layer_content", Description: "Get layer snapshot as image",
1940-
}, func(ctx context.Context, req *mcp.CallToolRequest, input LayerIDInput) (*mcp.CallToolResult, any, error) {
1941-
c, err := getClient(ctx)
1942-
if err != nil {
1943-
return nil, RawOutput{}, err
1944-
}
1945-
content, err := tools.GetLayerContent(ctx, c, input.LayerID)
1946-
if err != nil {
1947-
return nil, RawOutput{}, err
1948-
}
1949-
return nil, RawOutput{Result: content}, nil
1950-
})
1951-
19522035
// --- Workers ---
19532036
mcp.AddTool(server, &mcp.Tool{
19542037
Name: "worker_enable", Description: "Enable web worker tracking",

0 commit comments

Comments
 (0)