Skip to content

Commit 2500704

Browse files
committed
feat: add interception collector, list_intercepted_requests tool, bump 0.3.2
- InterceptionCollector captures Network.requestIntercepted events - New list_intercepted_requests tool shows pending intercepted requests - set_request_interception now uses collector for proper event handling - intercept_continue/intercept_with_response clean up pending list
1 parent d4dd2ec commit 2500704

4 files changed

Lines changed: 150 additions & 15 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.1",
3+
"version": "0.3.2",
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.1",
3+
"version": "0.3.2",
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: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ 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
23+
mu sync.Mutex
24+
client *webkit.Client
25+
networkMonitor *tools.NetworkMonitor
26+
consoleCollector *tools.ConsoleCollector
27+
timelineCollector *tools.TimelineCollector
28+
interceptionCollector *tools.InterceptionCollector
2829
}
2930

3031
var sess session
@@ -41,7 +42,7 @@ func getClient(ctx context.Context) (*webkit.Client, error) {
4142
func main() {
4243
server := mcp.NewServer(&mcp.Implementation{
4344
Name: "iwdp-mcp",
44-
Version: "0.3.1",
45+
Version: "0.3.2",
4546
}, nil)
4647

4748
registerTools(server)
@@ -557,6 +558,7 @@ func registerTools(server *mcp.Server) {
557558
sess.networkMonitor = nil
558559
sess.consoleCollector = nil
559560
sess.timelineCollector = nil
561+
sess.interceptionCollector = nil
560562
sess.mu.Unlock()
561563

562564
if oldClient != nil {
@@ -973,33 +975,76 @@ func registerTools(server *mcp.Server) {
973975
})
974976

975977
mcp.AddTool(server, &mcp.Tool{
976-
Name: "set_request_interception", Description: "Enable or disable request interception",
978+
Name: "set_request_interception", Description: "Enable or disable request interception. When enabled, intercepted requests appear in list_intercepted_requests and must be continued or responded to.",
977979
}, func(ctx context.Context, req *mcp.CallToolRequest, input SetRequestInterceptionInput) (*mcp.CallToolResult, any, error) {
978980
c, err := getClient(ctx)
979981
if err != nil {
980982
return nil, OKOutput{}, err
981983
}
982-
return nil, ok(), tools.SetRequestInterception(ctx, c, input.Enabled)
984+
if input.Enabled {
985+
sess.mu.Lock()
986+
if sess.interceptionCollector == nil {
987+
sess.interceptionCollector = tools.NewInterceptionCollector()
988+
}
989+
ic := sess.interceptionCollector
990+
sess.mu.Unlock()
991+
return nil, ok(), ic.Start(ctx, c)
992+
}
993+
sess.mu.Lock()
994+
ic := sess.interceptionCollector
995+
sess.mu.Unlock()
996+
if ic != nil {
997+
return nil, ok(), ic.Stop(ctx, c)
998+
}
999+
return nil, ok(), nil
1000+
})
1001+
1002+
mcp.AddTool(server, &mcp.Tool{
1003+
Name: "list_intercepted_requests", Description: "List pending intercepted requests. Each has a request_id to use with intercept_continue or intercept_with_response.",
1004+
}, func(ctx context.Context, req *mcp.CallToolRequest, _ EmptyInput) (*mcp.CallToolResult, any, error) {
1005+
sess.mu.Lock()
1006+
ic := sess.interceptionCollector
1007+
sess.mu.Unlock()
1008+
if ic == nil {
1009+
return nil, RawOutput{Result: []any{}}, nil
1010+
}
1011+
return nil, RawOutput{Result: ic.GetPending()}, nil
9831012
})
9841013

9851014
mcp.AddTool(server, &mcp.Tool{
986-
Name: "intercept_continue", Description: "Continue an intercepted request",
1015+
Name: "intercept_continue", Description: "Continue an intercepted request without modification",
9871016
}, func(ctx context.Context, req *mcp.CallToolRequest, input InterceptContinueInput) (*mcp.CallToolResult, any, error) {
9881017
c, err := getClient(ctx)
9891018
if err != nil {
9901019
return nil, OKOutput{}, err
9911020
}
992-
return nil, ok(), tools.InterceptContinue(ctx, c, input.RequestID)
1021+
err = tools.InterceptContinue(ctx, c, input.RequestID)
1022+
if err == nil {
1023+
sess.mu.Lock()
1024+
if sess.interceptionCollector != nil {
1025+
sess.interceptionCollector.RemovePending(input.RequestID)
1026+
}
1027+
sess.mu.Unlock()
1028+
}
1029+
return nil, ok(), err
9931030
})
9941031

9951032
mcp.AddTool(server, &mcp.Tool{
996-
Name: "intercept_with_response", Description: "Respond to an intercepted request with custom response",
1033+
Name: "intercept_with_response", Description: "Respond to an intercepted request with a custom response",
9971034
}, func(ctx context.Context, req *mcp.CallToolRequest, input InterceptWithResponseInput) (*mcp.CallToolResult, any, error) {
9981035
c, err := getClient(ctx)
9991036
if err != nil {
10001037
return nil, OKOutput{}, err
10011038
}
1002-
return nil, ok(), tools.InterceptWithResponse(ctx, c, input.RequestID, input.StatusCode, input.Headers, input.Body)
1039+
err = tools.InterceptWithResponse(ctx, c, input.RequestID, input.StatusCode, input.Headers, input.Body)
1040+
if err == nil {
1041+
sess.mu.Lock()
1042+
if sess.interceptionCollector != nil {
1043+
sess.interceptionCollector.RemovePending(input.RequestID)
1044+
}
1045+
sess.mu.Unlock()
1046+
}
1047+
return nil, ok(), err
10031048
})
10041049

10051050
mcp.AddTool(server, &mcp.Tool{

internal/tools/network.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,97 @@ func SetExtraHeaders(ctx context.Context, client *webkit.Client, headers map[str
161161
return err
162162
}
163163

164-
// SetRequestInterception enables or disables request interception.
164+
// InterceptedRequest holds an intercepted request waiting for a continue/response decision.
165+
type InterceptedRequest struct {
166+
RequestID string `json:"request_id"`
167+
Request webkit.NetworkRequest `json:"request"`
168+
}
169+
170+
// InterceptionCollector collects Network.requestIntercepted events.
171+
type InterceptionCollector struct {
172+
mu sync.Mutex
173+
pending map[string]*InterceptedRequest
174+
started bool
175+
}
176+
177+
// NewInterceptionCollector creates a new InterceptionCollector.
178+
func NewInterceptionCollector() *InterceptionCollector {
179+
return &InterceptionCollector{
180+
pending: make(map[string]*InterceptedRequest),
181+
}
182+
}
183+
184+
// Start enables request interception and registers the event handler.
185+
func (ic *InterceptionCollector) Start(ctx context.Context, client *webkit.Client) error {
186+
ic.mu.Lock()
187+
if ic.started {
188+
ic.mu.Unlock()
189+
return nil
190+
}
191+
ic.started = true
192+
ic.mu.Unlock()
193+
194+
_, err := client.Send(ctx, "Network.setInterceptionEnabled", map[string]interface{}{
195+
"enabled": true,
196+
})
197+
if err != nil {
198+
ic.mu.Lock()
199+
ic.started = false
200+
ic.mu.Unlock()
201+
return err
202+
}
203+
204+
client.OnEvent("Network.requestIntercepted", func(method string, params json.RawMessage) {
205+
var evt struct {
206+
RequestID string `json:"requestId"`
207+
Request webkit.NetworkRequest `json:"request"`
208+
}
209+
if err := json.Unmarshal(params, &evt); err != nil {
210+
return
211+
}
212+
ic.mu.Lock()
213+
ic.pending[evt.RequestID] = &InterceptedRequest{
214+
RequestID: evt.RequestID,
215+
Request: evt.Request,
216+
}
217+
ic.mu.Unlock()
218+
})
219+
220+
return nil
221+
}
222+
223+
// Stop disables request interception.
224+
func (ic *InterceptionCollector) Stop(ctx context.Context, client *webkit.Client) error {
225+
ic.mu.Lock()
226+
ic.started = false
227+
ic.pending = make(map[string]*InterceptedRequest)
228+
ic.mu.Unlock()
229+
_, err := client.Send(ctx, "Network.setInterceptionEnabled", map[string]interface{}{
230+
"enabled": false,
231+
})
232+
return err
233+
}
234+
235+
// GetPending returns all pending intercepted requests.
236+
func (ic *InterceptionCollector) GetPending() []InterceptedRequest {
237+
ic.mu.Lock()
238+
defer ic.mu.Unlock()
239+
result := make([]InterceptedRequest, 0, len(ic.pending))
240+
for _, req := range ic.pending {
241+
result = append(result, *req)
242+
}
243+
return result
244+
}
245+
246+
// RemovePending removes a request from the pending list (after continue/response).
247+
func (ic *InterceptionCollector) RemovePending(requestID string) {
248+
ic.mu.Lock()
249+
delete(ic.pending, requestID)
250+
ic.mu.Unlock()
251+
}
252+
253+
// SetRequestInterception enables or disables request interception directly.
254+
// Prefer using InterceptionCollector for the full interception workflow.
165255
func SetRequestInterception(ctx context.Context, client *webkit.Client, enabled bool) error {
166256
_, err := client.Send(ctx, "Network.setInterceptionEnabled", map[string]interface{}{
167257
"enabled": enabled,

0 commit comments

Comments
 (0)