diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index bed24802..a59705fa 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -169,7 +169,14 @@ func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWe Message: "the invocation started, but its final outcome could not be observed; do not retry automatically", } failure.InvocationId = nonEmptyString(result.InvocationID) - return oapi.InvokeWebMCPTool504JSONResponse(failure), nil + return oapi.InvokeWebMCPTool504JSONResponse{Body: failure, Headers: oapi.InvokeWebMCPTool504ResponseHeaders{XShouldRetry: "false"}}, nil + case errors.Is(err, webmcpclient.ErrToolPageUnloaded): + failure := oapi.WebMCPInvocationFailure{ + Code: oapi.ToolPageUnloaded, + Message: "the tool's page was unloaded before the tool responded; it may have had side effects, so do not retry automatically", + } + failure.InvocationId = nonEmptyString(result.InvocationID) + return oapi.InvokeWebMCPTool410JSONResponse{Body: failure, Headers: oapi.InvokeWebMCPTool410ResponseHeaders{XShouldRetry: "false"}}, nil default: logger.FromContext(ctx).Error("failed to invoke WebMCP tool", "err", err) return oapi.InvokeWebMCPTool500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to invoke WebMCP tool"}}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 22b062a4..be83459b 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" @@ -254,11 +256,37 @@ func TestInvokeWebMCPToolReportsUnknownOutcome(t *testing.T) { Body: &oapi.WebMCPInvokeRequest{ToolRef: "wmcp_test", Input: map[string]any{}}, }) require.NoError(t, err) - body := response.(oapi.InvokeWebMCPTool504JSONResponse) + recorder := httptest.NewRecorder() + require.NoError(t, response.VisitInvokeWebMCPToolResponse(recorder)) + require.Equal(t, http.StatusGatewayTimeout, recorder.Code) + require.Equal(t, "false", recorder.Header().Get("X-Should-Retry")) + var body oapi.WebMCPInvocationFailure + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &body)) require.Equal(t, oapi.OutcomeUnknown, body.Code) require.Equal(t, "invocation-1", *body.InvocationId) } +func TestInvokeWebMCPToolReportsUnloadedToolPage(t *testing.T) { + client := &fakeWebMCPClient{ + result: webmcpclient.InvocationResult{InvocationID: "invocation-1"}, + invokeErr: webmcpclient.ErrToolPageUnloaded, + } + service := &ApiService{webmcp: client} + + response, err := service.InvokeWebMCPTool(context.Background(), oapi.InvokeWebMCPToolRequestObject{ + Body: &oapi.WebMCPInvokeRequest{ToolRef: "wmcp_test", Input: map[string]any{}}, + }) + require.NoError(t, err) + recorder := httptest.NewRecorder() + require.NoError(t, response.VisitInvokeWebMCPToolResponse(recorder)) + require.Equal(t, http.StatusGone, recorder.Code) + require.Equal(t, "false", recorder.Header().Get("X-Should-Retry")) + var body oapi.WebMCPInvocationFailure + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &body)) + require.Equal(t, oapi.ToolPageUnloaded, body.Code) + require.Equal(t, "invocation-1", *body.InvocationId) +} + func TestGetWebMCPToolsReturnsNotFoundWithoutPage(t *testing.T) { service := &ApiService{webmcp: &fakeWebMCPClient{toolsErr: webmcpclient.ErrNoPageTarget}} response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) diff --git a/server/docs/repl.md b/server/docs/repl.md index 0f17f20d..81b8ec05 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -132,7 +132,7 @@ The reference below is generated from `runtime/browser-repl-help.ts`; edit that ### WebMCP methods - **`webmcp.listTools(options?)`** — Return tools registered across every open tab and embedded frame. Each result contains `tool_ref`, MCP-compatible `tool` metadata, and source window/tab/frame metadata. Set `options.excludeCustom` to omit custom tools. -- **`webmcp.invokeTool(toolRef, input?, options?)`** — Invoke one exact WebMCP registration without changing the attached target. `options.timeoutSec` bounds the request. Results have `invocation_id`, status, and optional output or error text. Do not automatically retry an `outcome_unknown` failure. +- **`webmcp.invokeTool(toolRef, input?, options?)`** — Invoke one exact WebMCP registration without changing the attached target. `options.timeoutSec` bounds the request. Results have `invocation_id`, status, and optional output or error text. Do not automatically retry an `outcome_unknown` or `tool_page_unloaded` failure. - **`webmcp.addCustomTools({ namespace, tools, forceOverwriteNamespace? })`** — Atomically add a non-empty batch of custom tools. Every definition requires `kind`, `match.url_patterns`, tool metadata, and an `execute` function; `outputSchema` is optional. Set `forceOverwriteNamespace` to replace every existing tool in that namespace. Returns the added tools with generated IDs. - **`webmcp.listCustomTools()`** — Return serializable summaries of every custom tool, including its generated ID and namespace. - **`webmcp.removeCustomTool(id)`** — Remove one custom tool by generated ID and return whether it existed. Active invocations continue. diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index db5e4f7e..69d08bd9 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -3007,7 +3007,8 @@ func (e TelemetryEventCategory) Valid() bool { // Defines values for WebMCPInvocationFailureCode. const ( - OutcomeUnknown WebMCPInvocationFailureCode = "outcome_unknown" + OutcomeUnknown WebMCPInvocationFailureCode = "outcome_unknown" + ToolPageUnloaded WebMCPInvocationFailureCode = "tool_page_unloaded" ) // Valid indicates whether the value is a known member of the WebMCPInvocationFailureCode enum. @@ -3015,6 +3016,8 @@ func (e WebMCPInvocationFailureCode) Valid() bool { switch e { case OutcomeUnknown: return true + case ToolPageUnloaded: + return true default: return false } @@ -16431,6 +16434,7 @@ type InvokeWebMCPToolResponse struct { JSON200 *WebMCPInvocationResult JSON400 *BadRequestError JSON404 *NotFoundError + JSON410 *WebMCPInvocationFailure JSON500 *InternalError JSON504 *WebMCPInvocationFailure } @@ -20091,6 +20095,13 @@ func ParseInvokeWebMCPToolResponse(rsp *http.Response) (*InvokeWebMCPToolRespons } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 410: + var dest WebMCPInvocationFailure + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON410 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -25451,6 +25462,23 @@ func (response InvokeWebMCPTool404JSONResponse) VisitInvokeWebMCPToolResponse(w return json.NewEncoder(w).Encode(response) } +type InvokeWebMCPTool410ResponseHeaders struct { + XShouldRetry string +} + +type InvokeWebMCPTool410JSONResponse struct { + Body WebMCPInvocationFailure + Headers InvokeWebMCPTool410ResponseHeaders +} + +func (response InvokeWebMCPTool410JSONResponse) VisitInvokeWebMCPToolResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Should-Retry", fmt.Sprint(response.Headers.XShouldRetry)) + w.WriteHeader(410) + + return json.NewEncoder(w).Encode(response.Body) +} + type InvokeWebMCPTool500JSONResponse struct{ InternalErrorJSONResponse } func (response InvokeWebMCPTool500JSONResponse) VisitInvokeWebMCPToolResponse(w http.ResponseWriter) error { @@ -25460,13 +25488,21 @@ func (response InvokeWebMCPTool500JSONResponse) VisitInvokeWebMCPToolResponse(w return json.NewEncoder(w).Encode(response) } -type InvokeWebMCPTool504JSONResponse WebMCPInvocationFailure +type InvokeWebMCPTool504ResponseHeaders struct { + XShouldRetry string +} + +type InvokeWebMCPTool504JSONResponse struct { + Body WebMCPInvocationFailure + Headers InvokeWebMCPTool504ResponseHeaders +} func (response InvokeWebMCPTool504JSONResponse) VisitInvokeWebMCPToolResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Should-Retry", fmt.Sprint(response.Headers.XShouldRetry)) w.WriteHeader(504) - return json.NewEncoder(w).Encode(response) + return json.NewEncoder(w).Encode(response.Body) } type GetWebMCPToolsRequestObject struct { @@ -28322,242 +28358,245 @@ var swaggerSpec = []string{ "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", - "/x8AXVnhSTQGBAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/lib/webmcpclient/client.go b/server/lib/webmcpclient/client.go index 29ef39bb..95abe208 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -25,6 +25,11 @@ const ( maxCompletedInvocations = 256 maxAbandonedInvocations = 256 maxExceptionTextBytes = 64 << 10 + // unloadedPageGrace is how long an invocation keeps waiting after its + // page is known to be gone. Chromium answers a call across a navigation + // the tool started by the time the replacement document loads, and never + // answers when the document was replaced by anything else or crashed. + unloadedPageGrace = 500 * time.Millisecond ) type connection struct { @@ -40,7 +45,7 @@ type connection struct { toolRefs map[string]string toolLimitWarned map[string]bool invocations map[invocationKey]invocationResponse - waitingInvocations map[invocationKey]string + waitingInvocations map[invocationKey]waitingInvocation abandonedInvocations map[invocationKey]time.Time polyfillBridges map[string]*polyfillBridge polyfillSyncMu sync.Mutex @@ -64,7 +69,7 @@ func newConnection(protocol *cdpclient.Client) *connection { toolRefs: make(map[string]string), toolLimitWarned: make(map[string]bool), invocations: make(map[invocationKey]invocationResponse), - waitingInvocations: make(map[invocationKey]string), + waitingInvocations: make(map[invocationKey]waitingInvocation), abandonedInvocations: make(map[invocationKey]time.Time), polyfillBridges: make(map[string]*polyfillBridge), stateChangedCh: make(chan struct{}, 1), @@ -118,6 +123,12 @@ func (c *connection) eventLoop(events <-chan browsersurface.Event) { c.removeSession(event.SessionID) case browsersurface.EventDocumentChanged: c.stateMu.Lock() + for key, waiting := range c.waitingInvocations { + if key.sessionID == event.SessionID && waiting.frameID == event.FrameID { + waiting.documentReplaced = true + c.waitingInvocations[key] = waiting + } + } c.removeFrameToolsLocked(event.SessionID, event.FrameID) c.stateMu.Unlock() c.signalStateChanged() @@ -185,6 +196,33 @@ func (c *connection) handleProtocolEvent(message cdpclient.Message) { c.stateMu.Unlock() c.signalStateChanged() } + case "Page.frameStoppedLoading": + var event struct { + FrameID string `json:"frameId"` + } + if json.Unmarshal(message.Params, &event) == nil { + c.stateMu.Lock() + for key, waiting := range c.waitingInvocations { + if key.sessionID == message.SessionID && waiting.frameID == event.FrameID && waiting.documentReplaced { + waiting.unloadedAt = time.Now() + c.waitingInvocations[key] = waiting + } + } + c.stateMu.Unlock() + } + case "Inspector.targetCrashed": + // The crashed renderer took every document in this session with it, + // so its tools can never respond. Chromium keeps the crashed tab open. + c.stateMu.Lock() + for key, waiting := range c.waitingInvocations { + if key.sessionID == message.SessionID && waiting.unloadedAt.IsZero() { + waiting.unloadedAt = time.Now() + c.waitingInvocations[key] = waiting + } + } + c.removeSessionToolsLocked(message.SessionID) + c.stateMu.Unlock() + c.signalStateChanged() case "WebMCP.toolResponded": var response invocationResponse if json.Unmarshal(message.Params, &response) == nil { @@ -488,7 +526,7 @@ func (c *connection) invoke(ctx context.Context, toolRef string, input map[strin }, nil } c.stateMu.Lock() - c.waitingInvocations[key] = frameID + c.waitingInvocations[key] = waitingInvocation{frameID: frameID} c.stateMu.Unlock() ticker := time.NewTicker(25 * time.Millisecond) defer ticker.Stop() @@ -509,6 +547,11 @@ func (c *connection) invoke(ctx context.Context, toolRef string, input map[strin c.stateMu.Unlock() return InvocationResult{InvocationID: started.InvocationID}, ErrOutcomeUnknown } + if unloadedAt := c.waitingInvocations[key].unloadedAt; !unloadedAt.IsZero() && time.Since(unloadedAt) >= unloadedPageGrace { + c.forceAbandonInvocationLocked(key) + c.stateMu.Unlock() + return InvocationResult{InvocationID: started.InvocationID}, ErrToolPageUnloaded + } c.stateMu.Unlock() if !c.sessionExists(sessionID) || c.executionClosed() { c.stateMu.Lock() @@ -569,18 +612,22 @@ func (c *connection) removeSession(sessionID string) { c.abandonInvocationLocked(key) } } + c.removeSessionToolsLocked(sessionID) + c.signalStateChanged() +} + +func (c *connection) removeSessionToolsLocked(sessionID string) { for ref, tool := range c.tools { if tool.sessionID == sessionID { delete(c.toolRefs, toolKey(tool.sessionID, tool.frameID, tool.registeredName)) delete(c.tools, ref) } } - c.signalStateChanged() } func (c *connection) abandonFrameInvocationsAcrossSessionsLocked(frameID string) { - for key, invocationFrameID := range c.waitingInvocations { - if invocationFrameID == frameID { + for key, waiting := range c.waitingInvocations { + if waiting.frameID == frameID { c.abandonInvocationLocked(key) } } diff --git a/server/lib/webmcpclient/client_test.go b/server/lib/webmcpclient/client_test.go index b22c3ebc..585b61ee 100644 --- a/server/lib/webmcpclient/client_test.go +++ b/server/lib/webmcpclient/client_test.go @@ -61,6 +61,9 @@ type fakeCDP struct { detachBeforeResponse bool navigateBeforeResult bool parentNavigateBeforeResult bool + reloadWithoutResult bool + crashBeforeResult bool + reloadAfterCrash bool nonAutosubmitDeclarative bool invokeResponseDelay time.Duration toolCount int @@ -286,6 +289,9 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) { nonAutosubmitDeclarative := f.nonAutosubmitDeclarative navigateBeforeResult := f.navigateBeforeResult parentNavigateBeforeResult := f.parentNavigateBeforeResult + reloadWithoutResult := f.reloadWithoutResult + crashBeforeResult := f.crashBeforeResult + reloadAfterCrash := f.reloadAfterCrash detachAfterResponse := f.detachAfterResponse detachBeforeResponse := f.detachBeforeResponse f.mu.Unlock() @@ -307,6 +313,44 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) { }) continue } + if crashBeforeResult || reloadWithoutResult { + // Let the invocation start waiting first. A page that goes + // away before that is outside these tests. + time.Sleep(100 * time.Millisecond) + } + if crashBeforeResult { + write(map[string]any{"method": "Inspector.targetCrashed", "sessionId": request.SessionID, "params": map[string]any{}}) + if reloadAfterCrash { + // The recovery document commits but never finishes loading. + write(map[string]any{ + "method": "Page.frameNavigated", "sessionId": request.SessionID, + "params": map[string]any{"frame": map[string]any{ + "id": "iframe-frame", "loaderId": "recovery-loader", "url": "https://payments.example/element", + }}, + }) + } + continue + } + if reloadWithoutResult { + // Chromium only answers an invocation across a navigation that the + // tool itself started. A reload from anywhere else replaces the + // document and never produces WebMCP.toolResponded. + write(map[string]any{ + "method": "Page.frameStartedLoading", "sessionId": request.SessionID, + "params": map[string]any{"frameId": "iframe-frame"}, + }) + write(map[string]any{ + "method": "Page.frameNavigated", "sessionId": request.SessionID, + "params": map[string]any{"frame": map[string]any{ + "id": "iframe-frame", "loaderId": "reload-loader", "url": "https://payments.example/element", + }}, + }) + write(map[string]any{ + "method": "Page.frameStoppedLoading", "sessionId": request.SessionID, + "params": map[string]any{"frameId": "iframe-frame"}, + }) + continue + } if !omitResponse { if parentNavigateBeforeResult { write(map[string]any{ @@ -337,6 +381,10 @@ func (f *fakeCDP) serve(w http.ResponseWriter, r *http.Request) { "id": "iframe-frame", "loaderId": "next-loader", "url": "https://payments.example/success", }}, }) + write(map[string]any{ + "method": "Page.frameStoppedLoading", "sessionId": request.SessionID, + "params": map[string]any{"frameId": "iframe-frame"}, + }) } output := any(map[string]any{"content": []map[string]any{{"type": "text", "text": request.SessionID}}}) write(map[string]any{ @@ -559,6 +607,62 @@ func TestInvocationPreservesResponseObservedAfterFrameNavigation(t *testing.T) { require.Equal(t, "iframe-session", result.Output.(map[string]any)["content"].([]any)[0].(map[string]any)["text"]) } +func TestInvocationFailsFastWhenToolDocumentReloadsWithoutResult(t *testing.T) { + fake := newFakeCDP(t, false) + fake.reloadWithoutResult = true + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + toolRef := paymentToolRef(t, manager) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + started := time.Now() + result, err := manager.Invoke(ctx, toolRef, map[string]any{}) + require.ErrorIs(t, err, ErrToolPageUnloaded) + require.Equal(t, "invocation-1", result.InvocationID) + require.Less(t, time.Since(started), 2*time.Second) +} + +func TestInvocationFailsFastWhenToolRendererCrashes(t *testing.T) { + fake := newFakeCDP(t, false) + fake.crashBeforeResult = true + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + toolRef := paymentToolRef(t, manager) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + started := time.Now() + result, err := manager.Invoke(ctx, toolRef, map[string]any{}) + require.ErrorIs(t, err, ErrToolPageUnloaded) + require.Equal(t, "invocation-1", result.InvocationID) + require.Less(t, time.Since(started), 2*time.Second) + + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + for _, tool := range tools { + require.NotEqual(t, "payment_tool", tool.Name) + } + _, err = manager.Invoke(context.Background(), toolRef, map[string]any{}) + require.ErrorIs(t, err, ErrToolNotFound) +} + +func TestInvocationFailsFastWhenCrashedToolPageReloads(t *testing.T) { + fake := newFakeCDP(t, false) + fake.crashBeforeResult = true + fake.reloadAfterCrash = true + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + toolRef := paymentToolRef(t, manager) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + started := time.Now() + _, err := manager.Invoke(ctx, toolRef, map[string]any{}) + require.ErrorIs(t, err, ErrToolPageUnloaded) + require.Less(t, time.Since(started), 2*time.Second) +} + func TestInvocationPreservesIframeResponseAfterParentNavigation(t *testing.T) { fake := newFakeCDP(t, false) fake.parentNavigateBeforeResult = true @@ -654,7 +758,7 @@ func TestConnectionDeathMidInvokeHasUnknownOutcome(t *testing.T) { func TestToolResponsesAreScopedBySession(t *testing.T) { client := &connection{ invocations: make(map[invocationKey]invocationResponse), - waitingInvocations: make(map[invocationKey]string), + waitingInvocations: make(map[invocationKey]waitingInvocation), abandonedInvocations: make(map[invocationKey]time.Time), stateChangedCh: make(chan struct{}, 1), } @@ -672,7 +776,7 @@ func TestToolResponsesAreScopedBySession(t *testing.T) { func TestToolResponseDecodesLatin1JSONOutput(t *testing.T) { client := &connection{ invocations: make(map[invocationKey]invocationResponse), - waitingInvocations: make(map[invocationKey]string), + waitingInvocations: make(map[invocationKey]waitingInvocation), abandonedInvocations: make(map[invocationKey]time.Time), stateChangedCh: make(chan struct{}, 1), } diff --git a/server/lib/webmcpclient/types.go b/server/lib/webmcpclient/types.go index ff6d5743..2d26fb7d 100644 --- a/server/lib/webmcpclient/types.go +++ b/server/lib/webmcpclient/types.go @@ -2,12 +2,16 @@ package webmcpclient import ( "errors" + "time" ) var ( ErrNoPageTarget = errors.New("no browser tabs found") ErrToolNotFound = errors.New("WebMCP tool not found") ErrOutcomeUnknown = errors.New("WebMCP invocation outcome is unknown") + // ErrToolPageUnloaded means the document that owned an invocation was + // replaced before the tool responded. The tool may have had side effects. + ErrToolPageUnloaded = errors.New("WebMCP tool page was unloaded before the tool responded") ) type Tool struct { @@ -87,6 +91,14 @@ type exceptionDetails struct { Description string `json:"description"` } +type waitingInvocation struct { + frameID string + documentReplaced bool + // unloadedAt is when the tool's page was known to be gone: its + // replacement document finished loading or its renderer crashed. + unloadedAt time.Time +} + type invocationKey struct { sessionID string invocationID string diff --git a/server/openapi.yaml b/server/openapi.yaml index 0b4cee76..186ca585 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1420,7 +1420,11 @@ paths: tools return after their fields are populated with an awaiting_submission status. Other tools wait for a terminal result, including across navigation. CDP-backed custom tool outputs above 240 KiB return an error rather than a truncated result. If the request times out after invocation - begins, the server reports outcome_unknown and never retries the tool automatically. + begins, the server reports outcome_unknown. If the tool's document is reloaded or replaced by a + navigation the tool did not start, and the tool never responds, the server reports + tool_page_unloaded once the replacement document loads. It also reports tool_page_unloaded + when the tool's renderer crashes. The server never retries the tool + automatically, and both failures set X-Should-Retry to false. operationId: invokeWebMCPTool x-telemetry-category: control requestBody: @@ -1441,8 +1445,26 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "410": + description: Invocation began, but the tool's document was replaced before the tool responded. The tool may have had side effects. + headers: + X-Should-Retry: + description: Always `false`. The tool may have had side effects, so retrying could repeat them. + schema: + type: string + enum: ["false"] + content: + application/json: + schema: + $ref: "#/components/schemas/WebMCPInvocationFailure" "504": description: Invocation began, but its final outcome could not be observed. + headers: + X-Should-Retry: + description: Always `false`. The tool may have completed, so retrying could repeat its side effects. + schema: + type: string + enum: ["false"] content: application/json: schema: @@ -7121,7 +7143,7 @@ components: properties: code: type: string - enum: [outcome_unknown] + enum: [outcome_unknown, tool_page_unloaded] message: type: string invocation_id: diff --git a/server/runtime/browser-repl-help.ts b/server/runtime/browser-repl-help.ts index c15b3d26..f38721c7 100644 --- a/server/runtime/browser-repl-help.ts +++ b/server/runtime/browser-repl-help.ts @@ -182,7 +182,7 @@ export const browserReplHelpRegistry = { invokeTool: { signature: 'webmcp.invokeTool(toolRef, input?, options?)', description: - 'Invoke one exact WebMCP registration without changing the attached target. `options.timeoutSec` bounds the request. Results have `invocation_id`, status, and optional output or error text. Do not automatically retry an `outcome_unknown` failure.', + 'Invoke one exact WebMCP registration without changing the attached target. `options.timeoutSec` bounds the request. Results have `invocation_id`, status, and optional output or error text. Do not automatically retry an `outcome_unknown` or `tool_page_unloaded` failure.', example: 'const result = await webmcp.invokeTool(tool.tool_ref, { query: "example" }, { timeoutSec: 30 });', }, addCustomTools: {