From 79ad6e8c88632f4c25695ad084fc717eb8235eb9 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:08:58 +0000 Subject: [PATCH 01/18] Add REPL-backed custom WebMCP tools --- server/cmd/api/api/browser_repl.go | 42 +- server/cmd/api/api/browser_repl_cells_test.go | 26 +- server/cmd/api/api/custom_webmcp.go | 158 ++ server/cmd/api/api/custom_webmcp_test.go | 97 + server/cmd/api/api/middleware.go | 12 +- server/cmd/api/api/middleware_test.go | 15 + server/docs/repl.md | 42 +- server/lib/events/category_gen.go | 2 + server/lib/oapi/oapi.go | 1770 +++++++++++------ server/openapi.yaml | 128 +- server/runtime/browser-cdp-client.ts | 16 + server/runtime/browser-repl.ts | 49 +- server/runtime/custom-webmcp.test.ts | 33 + server/runtime/custom-webmcp.ts | 748 +++++++ server/runtime/package-lock.json | 36 + server/runtime/package.json | 2 + 16 files changed, 2500 insertions(+), 676 deletions(-) create mode 100644 server/cmd/api/api/custom_webmcp.go create mode 100644 server/cmd/api/api/custom_webmcp_test.go create mode 100644 server/runtime/custom-webmcp.test.ts create mode 100644 server/runtime/custom-webmcp.ts diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 47826d91..0f4f2137 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -350,6 +350,7 @@ func (m *browserReplManager) killLocked(ctx context.Context, reason string) { type browserReplDaemonRequest struct { ID string `json:"id"` Code string `json:"code"` + Operation string `json:"operation,omitempty"` TimeoutMs int `json:"timeout_ms,omitempty"` } @@ -371,8 +372,9 @@ type browserReplDaemonResponse struct { // exception details and is exiting non-zero. The API treats it like a // timeout — terminate the handle and report repl_terminated — so the // state loss is explicit to the caller. - Exiting bool `json:"exiting,omitempty"` - DurationMs int `json:"duration_ms"` + Exiting bool `json:"exiting,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + DurationMs int `json:"duration_ms"` } // browserReplRequest is the already-encoded request sent over the daemon @@ -387,6 +389,10 @@ type browserReplRequest struct { // disabled. The daemon's limit applies to the line without its trailing // newline, so the encoded request must fit before it is sent. func prepareBrowserReplRequest(code string, timeout time.Duration) (*browserReplRequest, error) { + return prepareBrowserReplOperation(code, "", timeout) +} + +func prepareBrowserReplOperation(code, operation string, timeout time.Duration) (*browserReplRequest, error) { id := uuid.New().String() var buf bytes.Buffer encoder := json.NewEncoder(&buf) @@ -394,6 +400,7 @@ func prepareBrowserReplRequest(code string, timeout time.Duration) (*browserRepl if err := encoder.Encode(browserReplDaemonRequest{ ID: id, Code: code, + Operation: operation, TimeoutMs: int(timeout.Milliseconds()), }); err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) @@ -531,18 +538,30 @@ func browserReplTerminatedResponse(replID string, err error, durationMs int) oap } // StrictBrowserReplBodyMiddleware enforces additionalProperties: false on -// POST /repl. The generated strict-server decoder silently drops -// unknown fields, so without this middleware a request like -// {"code":"1","bogus":1} would be accepted despite the published schema. -// Malformed JSON and type errors are left to the strict handler's own 400 -// handling; only unknown fields are policed here. +// Browser REPL-owned request bodies. The generated strict-server decoder +// silently drops unknown fields, so malformed extensions need an explicit +// check before dispatch. func StrictBrowserReplBodyMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost || r.URL.Path != "/repl" || r.Body == nil { + if r.Body == nil { next.ServeHTTP(w, r) return } - limitedBody := http.MaxBytesReader(w, r.Body, maxBrowserReplBodyBytes) + var probe any + maxBytes := int64(0) + switch { + case r.Method == http.MethodPost && r.URL.Path == "/repl": + probe = &oapi.BrowserReplRequest{} + maxBytes = maxBrowserReplBodyBytes + case r.Method == http.MethodPut && r.URL.Path == "/webmcp/custom-tools": + probe = &oapi.CustomWebMCPRegistryRequest{} + maxBytes = maxCustomWebMCPRequestBytes + default: + next.ServeHTTP(w, r) + return + } + + limitedBody := http.MaxBytesReader(w, r.Body, maxBytes) body, err := io.ReadAll(limitedBody) _ = r.Body.Close() if err != nil { @@ -551,7 +570,7 @@ func StrictBrowserReplBodyMiddleware(next http.Handler) http.Handler { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusRequestEntityTooLarge) _ = json.NewEncoder(w).Encode(oapi.BadRequestError{ - Message: fmt.Sprintf("request body exceeds %d bytes", maxBrowserReplBodyBytes), + Message: fmt.Sprintf("request body exceeds %d bytes", maxBytes), }) return } @@ -562,8 +581,7 @@ func StrictBrowserReplBodyMiddleware(next http.Handler) http.Handler { dec := json.NewDecoder(bytes.NewReader(body)) dec.DisallowUnknownFields() - var probe oapi.BrowserReplRequest - if err := dec.Decode(&probe); err != nil && strings.HasPrefix(err.Error(), "json: unknown field") { + if err := dec.Decode(probe); err != nil && strings.HasPrefix(err.Error(), "json: unknown field") { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) _ = json.NewEncoder(w).Encode(oapi.BadRequestError{ diff --git a/server/cmd/api/api/browser_repl_cells_test.go b/server/cmd/api/api/browser_repl_cells_test.go index 4de6b845..4b48887e 100644 --- a/server/cmd/api/api/browser_repl_cells_test.go +++ b/server/cmd/api/api/browser_repl_cells_test.go @@ -41,14 +41,16 @@ func TestBrowserReplHelpEmitsAndReturnsMethodDocumentation(t *testing.T) { requireExec(t, svc, `repl.write(JSON.stringify(clickHelp.includes("visible, enabled, stable")))`, true) } -func TestBrowserReplCanPersistPatchrightAndPlaywrightCoreImports(t *testing.T) { +func TestBrowserReplCanPersistPinnedRuntimeImports(t *testing.T) { svc := newBrowserReplSvc(t) - requireExec(t, svc, `var playwright = await import("patchright"); var playwrightReference = playwright; var vanillaPlaywright = await import("playwright-core")`, nil) - requireExec(t, svc, `repl.write(JSON.stringify({ same: playwright === playwrightReference, patchrightConnect: typeof playwright.chromium.connectOverCDP, playwrightConnect: typeof vanillaPlaywright.chromium.connectOverCDP, endpoint: process.env.CDP_ENDPOINT }))`, map[string]interface{}{ - "same": true, - "patchrightConnect": "function", - "playwrightConnect": "function", - "endpoint": "ws://127.0.0.1:9222", + requireExec(t, svc, `var playwright = await import("patchright"); var playwrightReference = playwright; var vanillaPlaywright = await import("playwright-core"); var mcpCore = await import("@modelcontextprotocol/core"); var mcpServer = await import("@modelcontextprotocol/server/validators/ajv")`, nil) + requireExec(t, svc, `repl.write(JSON.stringify({ same: playwright === playwrightReference, patchrightConnect: typeof playwright.chromium.connectOverCDP, playwrightConnect: typeof vanillaPlaywright.chromium.connectOverCDP, toolSchema: typeof mcpCore.ToolSchema.safeParse, jsonSchemaValidator: typeof mcpServer.AjvJsonSchemaValidator, endpoint: process.env.CDP_ENDPOINT }))`, map[string]interface{}{ + "same": true, + "patchrightConnect": "function", + "playwrightConnect": "function", + "toolSchema": "function", + "jsonSchemaValidator": "function", + "endpoint": "ws://127.0.0.1:9222", }) } @@ -407,6 +409,16 @@ func TestStrictBrowserReplBodyMiddleware(t *testing.T) { handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/repl", strings.NewReader(`{nope`))) require.Equal(t, http.StatusOK, rec.Code) + rec = httptest.NewRecorder() + handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", strings.NewReader(`{"source":"","bogus":1}`))) + require.Equal(t, http.StatusBadRequest, rec.Code) + require.Contains(t, rec.Body.String(), `unknown field \"bogus\"`) + + rec = httptest.NewRecorder() + handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", strings.NewReader(`{"source":"customTools.register({})"}`))) + require.Equal(t, http.StatusOK, rec.Code) + require.JSONEq(t, `{"source":"customTools.register({})"}`, rec.Body.String()) + rec = httptest.NewRecorder() handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/repl", nil)) require.Equal(t, http.StatusOK, rec.Code) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go new file mode 100644 index 00000000..6c99de1c --- /dev/null +++ b/server/cmd/api/api/custom_webmcp.go @@ -0,0 +1,158 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/kernel/kernel-images/server/lib/logger" + "github.com/kernel/kernel-images/server/lib/oapi" +) + +const ( + customWebMCPOperationTimeout = 60 * time.Second + maxCustomWebMCPSourceBytes = 8_000_000 + maxCustomWebMCPRequestBytes = maxCustomWebMCPSourceBytes + (4 << 10) + customWebMCPGetOperation = "custom_tools_get" + customWebMCPReplaceOperation = "custom_tools_replace" +) + +type customWebMCPRuntimeSnapshot struct { + ReplID string `json:"repl_id"` + Revision int `json:"revision"` + Source string `json:"source"` + Tools []customWebMCPRuntimeDefinition `json:"tools"` + Installations []oapi.CustomWebMCPInstallation `json:"installations"` +} + +type customWebMCPRuntimeDefinition struct { + ID string `json:"id"` + Kind string `json:"kind"` + Match oapi.CustomWebMCPMatch `json:"match"` + Tool map[string]any `json:"tool"` + OutputSchema *map[string]any `json:"outputSchema,omitempty"` + Revision int `json:"revision"` +} + +func (s *ApiService) GetCustomWebMCPTools(ctx context.Context, _ oapi.GetCustomWebMCPToolsRequestObject) (oapi.GetCustomWebMCPToolsResponseObject, error) { + snapshot, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPGetOperation, "") + if err != nil { + logger.FromContext(ctx).Error("failed to get custom WebMCP tools", "err", err) + return oapi.GetCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to get custom WebMCP tools"}}, nil + } + return oapi.GetCustomWebMCPTools200JSONResponse(snapshot), nil +} + +func (s *ApiService) ReplaceCustomWebMCPTools(ctx context.Context, request oapi.ReplaceCustomWebMCPToolsRequestObject) (oapi.ReplaceCustomWebMCPToolsResponseObject, error) { + if request.Body == nil { + return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil + } + if len(request.Body.Source) > maxCustomWebMCPSourceBytes { + return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source must not exceed 8000000 bytes"}}, nil + } + RecordTelemetryCode(ctx, request.Body.Source) + + snapshot, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPReplaceOperation, request.Body.Source) + if err != nil { + var executionErr *customWebMCPExecutionError + if errors.As(err, &executionErr) { + return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: executionErr.Error()}}, nil + } + logger.FromContext(ctx).Error("failed to replace custom WebMCP tools", "err", err) + return oapi.ReplaceCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to replace custom WebMCP tools"}}, nil + } + return oapi.ReplaceCustomWebMCPTools200JSONResponse(snapshot), nil +} + +type customWebMCPExecutionError struct { + message string +} + +func (e *customWebMCPExecutionError) Error() string { return e.message } + +func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operation, source string) (oapi.CustomWebMCPRegistry, error) { + if err := m.acquire(ctx); err != nil { + return oapi.CustomWebMCPRegistry{}, err + } + defer m.release() + + operationCtx, cancelOperation, stopPropagation := m.operationContext(ctx) + defer func() { + stopPropagation() + cancelOperation(nil) + }() + if err := context.Cause(operationCtx); err != nil { + return oapi.CustomWebMCPRegistry{}, err + } + ctx = operationCtx + + request, err := prepareBrowserReplOperation(source, operation, customWebMCPOperationTimeout) + if err != nil { + return oapi.CustomWebMCPRegistry{}, err + } + if err := m.ensureLocked(ctx); err != nil { + return oapi.CustomWebMCPRegistry{}, fmt.Errorf("start Browser REPL: %w", err) + } + replID := m.child.id + response, err := m.executeLocked(ctx, request, customWebMCPOperationTimeout) + if err != nil { + var notDispatched *browserReplNotDispatchedError + if errors.As(err, ¬Dispatched) { + return oapi.CustomWebMCPRegistry{}, notDispatched.cause + } + var timeoutErr *browserReplTimeoutError + if errors.As(err, &timeoutErr) { + m.killLocked(ctx, "custom WebMCP operation timeout") + } else { + m.terminateLocked(ctx, "custom WebMCP operation failure") + } + return oapi.CustomWebMCPRegistry{}, err + } + if response.TimedOut || response.Exiting { + m.terminateLocked(ctx, "custom WebMCP operation terminated Browser REPL") + return oapi.CustomWebMCPRegistry{}, fmt.Errorf("Browser REPL %s terminated: %s", replID, response.Error) + } + if !response.Success { + return oapi.CustomWebMCPRegistry{}, &customWebMCPExecutionError{message: response.Error} + } + if len(response.Result) == 0 { + return oapi.CustomWebMCPRegistry{}, errors.New("Browser REPL returned no custom WebMCP registry") + } + + var runtime customWebMCPRuntimeSnapshot + if err := json.Unmarshal(response.Result, &runtime); err != nil { + m.terminateLocked(ctx, "invalid custom WebMCP response") + return oapi.CustomWebMCPRegistry{}, fmt.Errorf("decode custom WebMCP registry: %w", err) + } + if runtime.ReplID != replID { + m.terminateLocked(ctx, "custom WebMCP repl_id mismatch") + return oapi.CustomWebMCPRegistry{}, fmt.Errorf("custom WebMCP repl_id mismatch: expected %s, got %s", replID, runtime.ReplID) + } + + tools := make([]oapi.CustomWebMCPDefinition, 0, len(runtime.Tools)) + for _, tool := range runtime.Tools { + if tool.Kind != "page" && tool.Kind != "cdp" { + return oapi.CustomWebMCPRegistry{}, fmt.Errorf("custom WebMCP tool %s has invalid kind %q", tool.ID, tool.Kind) + } + tools = append(tools, oapi.CustomWebMCPDefinition{ + Id: tool.ID, + Kind: tool.Kind, + Match: tool.Match, + Tool: tool.Tool, + OutputSchema: tool.OutputSchema, + Revision: tool.Revision, + }) + } + if runtime.Installations == nil { + runtime.Installations = make([]oapi.CustomWebMCPInstallation, 0) + } + return oapi.CustomWebMCPRegistry{ + ReplId: runtime.ReplID, + Revision: runtime.Revision, + Source: runtime.Source, + Tools: tools, + Installations: runtime.Installations, + }, nil +} diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go new file mode 100644 index 00000000..7d0f5eaf --- /dev/null +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -0,0 +1,97 @@ +package api + +import ( + "context" + "testing" + + "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/stretchr/testify/require" +) + +const customWebMCPTestSource = ` +customTools.register({ + id: "test/read-title", + kind: "cdp", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_title", + description: "Read the current page title.", + inputSchema: {type: "object", additionalProperties: false}, + annotations: {readOnlyHint: true}, + }, + outputSchema: { + type: "object", + properties: {title: {type: "string"}}, + required: ["title"], + additionalProperties: false, + }, + execute: async () => ({title: await js(() => document.title)}), +}); +` + +func TestCustomWebMCPRegistryUsesBrowserReplLifecycle(t *testing.T) { + svc := newBrowserReplSvc(t) + ctx := context.Background() + + replaced, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ + Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + }) + require.NoError(t, err) + body := replaced.(oapi.ReplaceCustomWebMCPTools200JSONResponse) + require.NotEmpty(t, body.ReplId) + require.Equal(t, 1, body.Revision) + require.Equal(t, customWebMCPTestSource, body.Source) + require.Len(t, body.Tools, 1) + require.Equal(t, "test/read-title", body.Tools[0].Id) + require.Equal(t, "cdp", body.Tools[0].Kind) + require.Equal(t, []string{"https://example.com/*"}, body.Tools[0].Match.UrlPatterns) + require.Empty(t, body.Installations) + + got, err := svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + current := got.(oapi.GetCustomWebMCPTools200JSONResponse) + require.Equal(t, body.ReplId, current.ReplId) + require.Equal(t, body.Revision, current.Revision) + require.Equal(t, body.Tools, current.Tools) + + replaced, err = svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ + Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + }) + require.NoError(t, err) + second := replaced.(oapi.ReplaceCustomWebMCPTools200JSONResponse) + require.Equal(t, 2, second.Revision) + require.Equal(t, 2, second.Tools[0].Revision) + + reset := true + resetResponse := executeBrowserRepl(t, svc, &oapi.ExecuteBrowserReplJSONRequestBody{Code: "", Reset: &reset}) + require.NotEqual(t, body.ReplId, resetResponse.ReplId) + got, err = svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + fresh := got.(oapi.GetCustomWebMCPTools200JSONResponse) + require.Equal(t, resetResponse.ReplId, fresh.ReplId) + require.Zero(t, fresh.Revision) + require.Empty(t, fresh.Tools) +} + +func TestCustomWebMCPInvalidReplacementPreservesRegistry(t *testing.T) { + svc := newBrowserReplSvc(t) + ctx := context.Background() + + _, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ + Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + }) + require.NoError(t, err) + + response, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ + Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: `customTools.register({id: "broken"})`}, + }) + require.NoError(t, err) + _, ok := response.(oapi.ReplaceCustomWebMCPTools400JSONResponse) + require.True(t, ok, "expected 400 response, got %T", response) + + got, err := svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + current := got.(oapi.GetCustomWebMCPTools200JSONResponse) + require.Len(t, current.Tools, 1) + require.Equal(t, "test/read-title", current.Tools[0].Id) +} diff --git a/server/cmd/api/api/middleware.go b/server/cmd/api/api/middleware.go index 2436caef..de31fe13 100644 --- a/server/cmd/api/api/middleware.go +++ b/server/cmd/api/api/middleware.go @@ -121,13 +121,15 @@ func apiCallEvent(operationID string) (string, oapi.TelemetryEventCategory) { return "platform_api_call", events.Platform } -// WebMCPRequestSizeMiddleware bounds invoke bodies before the generated JSON -// decoder materializes them. The allowance above the input limit covers the -// request envelope while keeping memory use bounded. +// WebMCPRequestSizeMiddleware bounds WebMCP request bodies before the generated +// JSON decoder materializes them. func WebMCPRequestSizeMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost && r.URL.Path == "/webmcp/invoke" { + switch { + case r.Method == http.MethodPost && r.URL.Path == "/webmcp/invoke": r.Body = http.MaxBytesReader(w, r.Body, maxWebMCPRequestBytes) + case r.Method == http.MethodPut && r.URL.Path == "/webmcp/custom-tools": + r.Body = http.MaxBytesReader(w, r.Body, maxCustomWebMCPRequestBytes) } next.ServeHTTP(w, r) }) @@ -152,7 +154,7 @@ func StrictResponseErrorHandler(w http.ResponseWriter, r *http.Request, err erro } func isWebMCPRequest(r *http.Request) bool { - return r.URL.Path == "/webmcp/tools" || r.URL.Path == "/webmcp/invoke" + return r.URL.Path == "/webmcp/tools" || r.URL.Path == "/webmcp/invoke" || r.URL.Path == "/webmcp/custom-tools" } func writeStrictError(w http.ResponseWriter, status int, message string) { diff --git a/server/cmd/api/api/middleware_test.go b/server/cmd/api/api/middleware_test.go index 7e1f4063..abce8e21 100644 --- a/server/cmd/api/api/middleware_test.go +++ b/server/cmd/api/api/middleware_test.go @@ -88,6 +88,21 @@ func TestWebMCPRequestSizeMiddlewareRejectsOversizedInvokeBody(t *testing.T) { require.Equal(t, http.StatusBadRequest, recorder.Code) } +func TestWebMCPRequestSizeMiddlewareRejectsOversizedCustomToolBody(t *testing.T) { + body := strings.NewReader(strings.Repeat("x", maxCustomWebMCPRequestBytes+1)) + request := httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", body) + recorder := httptest.NewRecorder() + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := io.ReadAll(r.Body) + var tooLarge *http.MaxBytesError + require.ErrorAs(t, err, &tooLarge) + w.WriteHeader(http.StatusBadRequest) + }) + + WebMCPRequestSizeMiddleware(next).ServeHTTP(recorder, request) + require.Equal(t, http.StatusBadRequest, recorder.Code) +} + func TestWebMCPRequestSizeMiddlewareDoesNotLimitOtherRoutes(t *testing.T) { body := strings.NewReader(strings.Repeat("x", maxWebMCPRequestBytes+1)) request := httptest.NewRequest(http.MethodPost, "/playwright/execute", body) diff --git a/server/docs/repl.md b/server/docs/repl.md index 5462aeeb..d51b4123 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -56,9 +56,9 @@ Use `{ "code": "", "reset": true }` to explicitly replace the REPL and clear all ## Runtime globals -The context preloads `repl`, captured `console` methods, every browser helper, `browser`, `webmcp`, timers, `queueMicrotask`, `Buffer`, `process`, `fetch`, `URL`, `URLSearchParams`, text encoders/decoders, abort controllers/signals, `structuredClone`, `atob`, `btoa`, and `crypto`. Node built-ins and installed packages are available through dynamic `import()`. +The context preloads `repl`, captured `console` methods, every browser helper, `browser`, `webmcp`, `customTools`, timers, `queueMicrotask`, `Buffer`, `process`, `fetch`, `URL`, `URLSearchParams`, text encoders/decoders, abort controllers/signals, `structuredClone`, `atob`, `btoa`, and `crypto`. Node built-ins and installed packages are available through dynamic `import()`. -`repl`, `browser`, and `webmcp` are frozen objects. `webmcp === browser.webmcp`, and each bare browser helper is the same function exposed on `browser`. +`repl`, `browser`, `webmcp`, and `customTools` are frozen objects. `webmcp === browser.webmcp`, and each bare browser helper is the same function exposed on `browser`. ## Output @@ -166,6 +166,44 @@ Invocation results have `invocation_id`, `status`, and optional `output` or `err Every WebMCP request is bound to the active Browser REPL execution and is aborted slightly before its destructive deadline, allowing an awaited request to return a normal failure while preserving the REPL. Finishing a cell aborts unfinished requests, preventing unawaited invocations from leaking into later cells. +## Custom WebMCP tools + +`GET /webmcp/custom-tools` returns the registry held by the current Browser REPL. `PUT /webmcp/custom-tools` atomically replaces it with JavaScript source that calls `customTools.register(...)`. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. + +Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. + +```js +customTools.register({ + id: "example/read-title", + kind: "cdp", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_title", + description: "Read the current page title.", + inputSchema: {type: "object", additionalProperties: false}, + annotations: {readOnlyHint: true}, + }, + outputSchema: { + type: "object", + properties: {title: {type: "string"}}, + required: ["title"], + additionalProperties: false, + }, + execute: async (_input, {matches}) => ({ + title: await js(() => document.title, {targetId: matches[0].top_target_id}), + }), +}); +``` + +The registry validates MCP metadata with `ToolSchema` and compiles input and optional output schemas with `AjvJsonSchemaValidator`. CDP-backed handlers validate output before resolving their page proxy; page-backed handlers follow native imperative WebMCP result handling because Chromium does not currently accept `outputSchema`. Both packages are pinned image dependencies and may also be imported directly: + +```js +var {ToolSchema} = await import("@modelcontextprotocol/core"); +var {AjvJsonSchemaValidator} = await import("@modelcontextprotocol/server/validators/ajv"); +``` + +Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A REPL reset or replacement clears the registry and changes `repl_id`. + ## Patchright and Playwright Core `patchright` and `playwright-core` are installed as pinned Browser REPL dependencies. Patchright matches the default engine used by the image's Playwright execution service; load it with dynamic `import()` and connect it to the existing Chromium over CDP instead of launching or downloading another browser: diff --git a/server/lib/events/category_gen.go b/server/lib/events/category_gen.go index 2b0e9310..c5a377a3 100644 --- a/server/lib/events/category_gen.go +++ b/server/lib/events/category_gen.go @@ -67,6 +67,7 @@ var categoryByOperationID = map[string]oapi.TelemetryEventCategory{ "ExecuteBrowserRepl": oapi.TelemetryEventCategory("control"), "ExecutePlaywrightCode": oapi.TelemetryEventCategory("control"), "FileInfo": oapi.TelemetryEventCategory("platform"), + "GetCustomWebMCPTools": oapi.TelemetryEventCategory("control"), "GetMousePosition": oapi.TelemetryEventCategory("control"), "GetTelemetry": oapi.TelemetryEventCategory("platform"), "GetWebMCPTools": oapi.TelemetryEventCategory("control"), @@ -93,6 +94,7 @@ var categoryByOperationID = map[string]oapi.TelemetryEventCategory{ "PutTelemetry": oapi.TelemetryEventCategory("platform"), "ReadClipboard": oapi.TelemetryEventCategory("control"), "ReadFile": oapi.TelemetryEventCategory("platform"), + "ReplaceCustomWebMCPTools": oapi.TelemetryEventCategory("control"), "Scroll": oapi.TelemetryEventCategory("control"), "SetCursor": oapi.TelemetryEventCategory("control"), "SetFilePermissions": oapi.TelemetryEventCategory("platform"), diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 09feae1e..dfdecc0d 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6258,6 +6258,45 @@ type CreateDirectoryRequest struct { Path string `json:"path"` } +// CustomWebMCPDefinition defines model for CustomWebMCPDefinition. +type CustomWebMCPDefinition struct { + Id string `json:"id"` + Kind string `json:"kind"` + Match CustomWebMCPMatch `json:"match"` + OutputSchema *map[string]interface{} `json:"output_schema,omitempty"` + Revision int `json:"revision"` + + // Tool MCP-compatible tool metadata as submitted to customTools.register(). + Tool map[string]interface{} `json:"tool"` +} + +// CustomWebMCPInstallation defines model for CustomWebMCPInstallation. +type CustomWebMCPInstallation struct { + DefinitionIds []string `json:"definition_ids"` + Errors []string `json:"errors"` + TargetId string `json:"target_id"` +} + +// CustomWebMCPMatch defines model for CustomWebMCPMatch. +type CustomWebMCPMatch struct { + UrlPatterns []string `json:"url_patterns"` +} + +// CustomWebMCPRegistry defines model for CustomWebMCPRegistry. +type CustomWebMCPRegistry struct { + Installations []CustomWebMCPInstallation `json:"installations"` + ReplId string `json:"repl_id"` + Revision int `json:"revision"` + Source string `json:"source"` + Tools []CustomWebMCPDefinition `json:"tools"` +} + +// CustomWebMCPRegistryRequest defines model for CustomWebMCPRegistryRequest. +type CustomWebMCPRegistryRequest struct { + // Source JavaScript evaluated in the existing Browser REPL with customTools and all Browser REPL globals available. + Source string `json:"source"` +} + // DeletePathRequest defines model for DeletePathRequest. type DeletePathRequest struct { // Path Absolute path to delete. @@ -7238,6 +7277,9 @@ type PutTelemetryJSONRequestBody = BrowserTelemetryConfig // PublishTelemetryEventJSONRequestBody defines body for PublishTelemetryEvent for application/json ContentType. type PublishTelemetryEventJSONRequestBody = PublishEventRequest +// ReplaceCustomWebMCPToolsJSONRequestBody defines body for ReplaceCustomWebMCPTools for application/json ContentType. +type ReplaceCustomWebMCPToolsJSONRequestBody = CustomWebMCPRegistryRequest + // InvokeWebMCPToolJSONRequestBody defines body for InvokeWebMCPTool for application/json ContentType. type InvokeWebMCPToolJSONRequestBody = WebMCPInvokeRequest @@ -9921,6 +9963,14 @@ type ClientInterface interface { // StreamTelemetryEvents request StreamTelemetryEvents(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetCustomWebMCPTools request + GetCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ReplaceCustomWebMCPToolsWithBody request with any body + ReplaceCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + ReplaceCustomWebMCPTools(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // InvokeWebMCPToolWithBody request with any body InvokeWebMCPToolWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -11022,6 +11072,42 @@ func (c *Client) StreamTelemetryEvents(ctx context.Context, params *StreamTeleme return c.Client.Do(req) } +func (c *Client) GetCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCustomWebMCPToolsRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ReplaceCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReplaceCustomWebMCPToolsRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ReplaceCustomWebMCPTools(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReplaceCustomWebMCPToolsRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) InvokeWebMCPToolWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewInvokeWebMCPToolRequestWithBody(c.Server, contentType, body) if err != nil { @@ -13403,6 +13489,73 @@ func NewStreamTelemetryEventsRequest(server string, params *StreamTelemetryEvent return req, nil } +// NewGetCustomWebMCPToolsRequest generates requests for GetCustomWebMCPTools +func NewGetCustomWebMCPToolsRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/webmcp/custom-tools") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewReplaceCustomWebMCPToolsRequest calls the generic ReplaceCustomWebMCPTools builder with application/json body +func NewReplaceCustomWebMCPToolsRequest(server string, body ReplaceCustomWebMCPToolsJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewReplaceCustomWebMCPToolsRequestWithBody(server, "application/json", bodyReader) +} + +// NewReplaceCustomWebMCPToolsRequestWithBody generates requests for ReplaceCustomWebMCPTools with any type of body +func NewReplaceCustomWebMCPToolsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/webmcp/custom-tools") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewInvokeWebMCPToolRequest calls the generic InvokeWebMCPTool builder with application/json body func NewInvokeWebMCPToolRequest(server string, body InvokeWebMCPToolJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -13753,6 +13906,14 @@ type ClientWithResponsesInterface interface { // StreamTelemetryEventsWithResponse request StreamTelemetryEventsWithResponse(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*StreamTelemetryEventsResponse, error) + // GetCustomWebMCPToolsWithResponse request + GetCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCustomWebMCPToolsResponse, error) + + // ReplaceCustomWebMCPToolsWithBodyWithResponse request with any body + ReplaceCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) + + ReplaceCustomWebMCPToolsWithResponse(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) + // InvokeWebMCPToolWithBodyWithResponse request with any body InvokeWebMCPToolWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InvokeWebMCPToolResponse, error) @@ -15142,6 +15303,53 @@ func (r StreamTelemetryEventsResponse) StatusCode() int { return 0 } +type GetCustomWebMCPToolsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CustomWebMCPRegistry + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r GetCustomWebMCPToolsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCustomWebMCPToolsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ReplaceCustomWebMCPToolsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CustomWebMCPRegistry + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ReplaceCustomWebMCPToolsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ReplaceCustomWebMCPToolsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type InvokeWebMCPToolResponse struct { Body []byte HTTPResponse *http.Response @@ -15978,6 +16186,32 @@ func (c *ClientWithResponses) StreamTelemetryEventsWithResponse(ctx context.Cont return ParseStreamTelemetryEventsResponse(rsp) } +// GetCustomWebMCPToolsWithResponse request returning *GetCustomWebMCPToolsResponse +func (c *ClientWithResponses) GetCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCustomWebMCPToolsResponse, error) { + rsp, err := c.GetCustomWebMCPTools(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCustomWebMCPToolsResponse(rsp) +} + +// ReplaceCustomWebMCPToolsWithBodyWithResponse request with arbitrary body returning *ReplaceCustomWebMCPToolsResponse +func (c *ClientWithResponses) ReplaceCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) { + rsp, err := c.ReplaceCustomWebMCPToolsWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseReplaceCustomWebMCPToolsResponse(rsp) +} + +func (c *ClientWithResponses) ReplaceCustomWebMCPToolsWithResponse(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) { + rsp, err := c.ReplaceCustomWebMCPTools(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseReplaceCustomWebMCPToolsResponse(rsp) +} + // InvokeWebMCPToolWithBodyWithResponse request with arbitrary body returning *InvokeWebMCPToolResponse func (c *ClientWithResponses) InvokeWebMCPToolWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InvokeWebMCPToolResponse, error) { rsp, err := c.InvokeWebMCPToolWithBody(ctx, contentType, body, reqEditors...) @@ -18216,6 +18450,79 @@ func ParseStreamTelemetryEventsResponse(rsp *http.Response) (*StreamTelemetryEve return response, nil } +// ParseGetCustomWebMCPToolsResponse parses an HTTP response from a GetCustomWebMCPToolsWithResponse call +func ParseGetCustomWebMCPToolsResponse(rsp *http.Response) (*GetCustomWebMCPToolsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetCustomWebMCPToolsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CustomWebMCPRegistry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseReplaceCustomWebMCPToolsResponse parses an HTTP response from a ReplaceCustomWebMCPToolsWithResponse call +func ParseReplaceCustomWebMCPToolsResponse(rsp *http.Response) (*ReplaceCustomWebMCPToolsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ReplaceCustomWebMCPToolsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CustomWebMCPRegistry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseInvokeWebMCPToolResponse parses an HTTP response from a InvokeWebMCPToolWithResponse call func ParseInvokeWebMCPToolResponse(rsp *http.Response) (*InvokeWebMCPToolResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -18486,6 +18793,12 @@ type ServerInterface interface { // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) StreamTelemetryEvents(w http.ResponseWriter, r *http.Request, params StreamTelemetryEventsParams) + // Get the Browser REPL custom WebMCP registry + // (GET /webmcp/custom-tools) + GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) + // Replace the Browser REPL custom WebMCP registry + // (PUT /webmcp/custom-tools) + ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) // Invoke a discovered WebMCP tool // (POST /webmcp/invoke) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) @@ -18846,6 +19159,18 @@ func (_ Unimplemented) StreamTelemetryEvents(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusNotImplemented) } +// Get the Browser REPL custom WebMCP registry +// (GET /webmcp/custom-tools) +func (_ Unimplemented) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Replace the Browser REPL custom WebMCP registry +// (PUT /webmcp/custom-tools) +func (_ Unimplemented) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // Invoke a discovered WebMCP tool // (POST /webmcp/invoke) func (_ Unimplemented) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) { @@ -19996,6 +20321,34 @@ func (siw *ServerInterfaceWrapper) StreamTelemetryEvents(w http.ResponseWriter, handler.ServeHTTP(w, r) } +// GetCustomWebMCPTools operation middleware +func (siw *ServerInterfaceWrapper) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCustomWebMCPTools(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReplaceCustomWebMCPTools operation middleware +func (siw *ServerInterfaceWrapper) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReplaceCustomWebMCPTools(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // InvokeWebMCPTool operation middleware func (siw *ServerInterfaceWrapper) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) { @@ -20311,6 +20664,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/telemetry/stream", wrapper.StreamTelemetryEvents) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/webmcp/custom-tools", wrapper.GetCustomWebMCPTools) + }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/webmcp/custom-tools", wrapper.ReplaceCustomWebMCPTools) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/webmcp/invoke", wrapper.InvokeWebMCPTool) }) @@ -22763,6 +23122,66 @@ func (response StreamTelemetryEvents200TexteventStreamResponse) VisitStreamTelem } } +type GetCustomWebMCPToolsRequestObject struct { +} + +type GetCustomWebMCPToolsResponseObject interface { + VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error +} + +type GetCustomWebMCPTools200JSONResponse CustomWebMCPRegistry + +func (response GetCustomWebMCPTools200JSONResponse) VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } + +func (response GetCustomWebMCPTools500JSONResponse) VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type ReplaceCustomWebMCPToolsRequestObject struct { + Body *ReplaceCustomWebMCPToolsJSONRequestBody +} + +type ReplaceCustomWebMCPToolsResponseObject interface { + VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error +} + +type ReplaceCustomWebMCPTools200JSONResponse CustomWebMCPRegistry + +func (response ReplaceCustomWebMCPTools200JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ReplaceCustomWebMCPTools400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response ReplaceCustomWebMCPTools400JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ReplaceCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } + +func (response ReplaceCustomWebMCPTools500JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type InvokeWebMCPToolRequestObject struct { Body *InvokeWebMCPToolJSONRequestBody } @@ -23026,6 +23445,12 @@ type StrictServerInterface interface { // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) StreamTelemetryEvents(ctx context.Context, request StreamTelemetryEventsRequestObject) (StreamTelemetryEventsResponseObject, error) + // Get the Browser REPL custom WebMCP registry + // (GET /webmcp/custom-tools) + GetCustomWebMCPTools(ctx context.Context, request GetCustomWebMCPToolsRequestObject) (GetCustomWebMCPToolsResponseObject, error) + // Replace the Browser REPL custom WebMCP registry + // (PUT /webmcp/custom-tools) + ReplaceCustomWebMCPTools(ctx context.Context, request ReplaceCustomWebMCPToolsRequestObject) (ReplaceCustomWebMCPToolsResponseObject, error) // Invoke a discovered WebMCP tool // (POST /webmcp/invoke) InvokeWebMCPTool(ctx context.Context, request InvokeWebMCPToolRequestObject) (InvokeWebMCPToolResponseObject, error) @@ -24779,6 +25204,61 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re } } +// GetCustomWebMCPTools operation middleware +func (sh *strictHandler) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + var request GetCustomWebMCPToolsRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetCustomWebMCPTools(ctx, request.(GetCustomWebMCPToolsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetCustomWebMCPTools") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetCustomWebMCPToolsResponseObject); ok { + if err := validResponse.VisitGetCustomWebMCPToolsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReplaceCustomWebMCPTools operation middleware +func (sh *strictHandler) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + var request ReplaceCustomWebMCPToolsRequestObject + + var body ReplaceCustomWebMCPToolsJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReplaceCustomWebMCPTools(ctx, request.(ReplaceCustomWebMCPToolsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReplaceCustomWebMCPTools") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReplaceCustomWebMCPToolsResponseObject); ok { + if err := validResponse.VisitReplaceCustomWebMCPToolsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // InvokeWebMCPTool operation middleware func (sh *strictHandler) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) { var request InvokeWebMCPToolRequestObject @@ -24837,645 +25317,657 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request) // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "R9A7ZouOSjK3LXwTmHDrs/gl5MMg04s7feT++zBwV+wPgyO9ONJH7r8Pg3iMbRzXf6CGtRI1QelG8FRb", + "h0Tvt4RwC1pnO/wjG02WlkXo+cqH6MLPQ/IcrithGZyZHtG5IdKawk2xMVkS8KBxhh7oXeiEYdgdiaFe", + "1TU10WOrjs/dHf0oOCKxrD8e3vcsq6nue6i7YUn8MdHnTVoWrPlyePru5cn/z977L7eRI+mir4Jl3IiW", + "dklKdrt7Z+zo2Ctbcre2LVshyTO7PewQwSqQRKsI1BRQkuk+3jgPcZ7wPMkJZCZQP1hFFimpbc/u/NEj", + "SwUUCshMJBKZ33d10uv3/npxCv9/fPLmBH64OHl7dHbSGXKz1QX+Wek7tZIN2by+x82UH4FoOySjUcWJ", + "oAsW8gSQoAaxlAuIDh5AfnjCLP+glV5A/QZ1gwCD5fxwLCghSIVxzC3HJHOdLWCz0yqsNXilbigTkeg7", + "tod5ATgkTBig9NVx+zyM+ywTM57FkHoJSZqapfkkkYDdJu2QveJJIrJB8UuaAMhifXd5xQ7C6A/oTx55", + "LMA8+bQ9YOMBaCYjBBvXxhIiHHcyE8zMeSqA6kvGgfUygsF4/I5yOa80YYI9OEpEFOHfGA9b7fPIwOte", + "x9kSKE/XZ11WiID7vj79Opq7YaqZuPYu5fryE2z2yrdCP7jeKZSkb9XhpWuxrjOIajlV6tzbJbYI3RW1", + "UJ1LbKptoZikS1t4sNzWLV/X5sfh2dADVowQYd+GDvDZEhtn0T7Rs26t3+iZb1uqSsG0sA09nBbPQ4pM", + "Uz+QpNK1l5/FsqkPzMu4NsLaZKNUlLrDJJZLbOY7TuStuL6V4q7jIr+Rt+IvUtzVVrropvN6+55WF50K", + "bUpdbfzMM2xyXGpR700qGUjkOnV2qiQRrtW7CujtW/V34Vtt6HTr/lb7Kldwd+mqKOrzPVGF1rWMk02k", + "WERLdhonot7aWX+pZt2mifp5g22qk+Q7zHwAq0tPFLda7QPBCrp2gk/7XlI+K/CYN7Cy8lkVNpxaxxpY", + "DYEAws1Qp46O9YJyL99Ak0qPiHDQ0SS43t5Ag5otqHQ1l1PbvSP3dLWbKO3SOkqrrTTvNHrNq4MuMB86", + "tH4bHm7pY4t5LPpqmkvLJ9c6FapTV1d88g6eDV2sELtvz5sfWDq7bKB1Muv+Cv741tjuvf4KZOq2aLQP", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 87a26a85..cc70b856 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1422,6 +1422,52 @@ paths: "500": $ref: "#/components/responses/InternalError" + /webmcp/custom-tools: + get: + summary: Get the Browser REPL custom WebMCP registry + description: | + Returns custom tool definitions held by the current Browser REPL process, their installation + status across open tabs, and the repl_id that owns the state. A reset, timeout, crash, OOM, or + API restart replaces the REPL and clears this registry. + operationId: getCustomWebMCPTools + x-telemetry-category: control + responses: + "200": + description: Current REPL-local custom WebMCP registry. + content: + application/json: + schema: + $ref: "#/components/schemas/CustomWebMCPRegistry" + "500": + $ref: "#/components/responses/InternalError" + put: + summary: Replace the Browser REPL custom WebMCP registry + description: | + Evaluates JavaScript source in the existing persistent Browser REPL. The source registers + page-backed or CDP-backed definitions with customTools.register(...). Definitions are staged + and replace the live registry only after the source evaluates successfully. Matchers apply to + every top-level document, nested frame, and OOPIF, and matching tools are registered on the + corresponding topmost document through native imperative WebMCP. + operationId: replaceCustomWebMCPTools + x-telemetry-category: control + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CustomWebMCPRegistryRequest" + responses: + "200": + description: Replaced REPL-local custom WebMCP registry. + content: + application/json: + schema: + $ref: "#/components/schemas/CustomWebMCPRegistry" + "400": + $ref: "#/components/responses/BadRequestError" + "500": + $ref: "#/components/responses/InternalError" + /playwright/execute: post: summary: Execute Playwright/TypeScript code against the browser @@ -1478,7 +1524,8 @@ paths: Browser-control methods are available both directly and on `browser`. The same frozen - browser-wide WebMCP client is available as `webmcp` and `browser.webmcp`. Common workflows: + browser-wide WebMCP client is available as `webmcp` and `browser.webmcp`. The `customTools` + registry global is shared with `GET|PUT /webmcp/custom-tools`. Common workflows: ```js await gotoUrl("https://example.com"); @@ -1494,8 +1541,9 @@ paths: Top-level bindings and dynamically imported modules persist until reset or process replacement. Expression values are ignored; emit output with `repl.write(...)`, console - methods, or `repl.emitImage(...)`. Pinned `patchright` and `playwright-core` packages are - available through dynamic `import()`, and `cdp()` provides unrestricted DevTools access. + methods, or `repl.emitImage(...)`. Pinned `patchright`, `playwright-core`, + `@modelcontextprotocol/core`, and `@modelcontextprotocol/server` packages are available through + dynamic `import()`, and `cdp()` provides unrestricted DevTools access. The runtime starts lazily and is owned by the API process. Reset, API restart, timeout, crash, OOM, or protocol failure destroys its state and changes its CUID2 `repl_id`. @@ -6406,6 +6454,80 @@ components: type: array items: $ref: "#/components/schemas/WebMCPTool" + CustomWebMCPRegistryRequest: + type: object + required: [source] + properties: + source: + type: string + maxLength: 8000000 + description: JavaScript evaluated in the existing Browser REPL with customTools and all Browser REPL globals available. + additionalProperties: false + CustomWebMCPMatch: + type: object + required: [url_patterns] + properties: + url_patterns: + type: array + minItems: 1 + items: + type: string + additionalProperties: false + CustomWebMCPDefinition: + type: object + required: [id, kind, match, tool, revision] + properties: + id: + type: string + kind: + type: string + pattern: "^(page|cdp)$" + match: + $ref: "#/components/schemas/CustomWebMCPMatch" + tool: + type: object + description: MCP-compatible tool metadata as submitted to customTools.register(). + additionalProperties: true + output_schema: + type: object + additionalProperties: true + revision: + type: integer + additionalProperties: false + CustomWebMCPInstallation: + type: object + required: [target_id, definition_ids, errors] + properties: + target_id: + type: string + definition_ids: + type: array + items: + type: string + errors: + type: array + items: + type: string + additionalProperties: false + CustomWebMCPRegistry: + type: object + required: [repl_id, revision, source, tools, installations] + properties: + repl_id: + type: string + revision: + type: integer + source: + type: string + tools: + type: array + items: + $ref: "#/components/schemas/CustomWebMCPDefinition" + installations: + type: array + items: + $ref: "#/components/schemas/CustomWebMCPInstallation" + additionalProperties: false WebMCPInvokeRequest: type: object additionalProperties: false diff --git a/server/runtime/browser-cdp-client.ts b/server/runtime/browser-cdp-client.ts index fcd8a688..787f7277 100644 --- a/server/runtime/browser-cdp-client.ts +++ b/server/runtime/browser-cdp-client.ts @@ -12,6 +12,7 @@ export interface CdpTarget { title: string; url: string; attached: boolean; + parentFrameId?: string; } export interface PendingDialog { @@ -136,6 +137,8 @@ export class BrowserReplCdpClient { private pending = new Map(); private events: CdpEvent[] = []; private eventWaiters = new Set(); + private eventSubscribers = new Set<(event: CdpEvent) => void>(); + private disconnectSubscribers = new Set<() => void>(); sessionId: string | null = null; targetId: string | null = null; @@ -252,6 +255,7 @@ export class BrowserReplCdpClient { } this.pending.clear(); for (const waiter of [...this.eventWaiters]) waiter.reject(err); + for (const subscriber of this.disconnectSubscribers) subscriber(); } private onMessage(data: unknown): void { @@ -329,6 +333,17 @@ export class BrowserReplCdpClient { waiter.reject(err instanceof Error ? err : new Error(String(err))); } } + for (const subscriber of this.eventSubscribers) subscriber(event); + } + + subscribeEvents(subscriber: (event: CdpEvent) => void): () => void { + this.eventSubscribers.add(subscriber); + return () => this.eventSubscribers.delete(subscriber); + } + + subscribeDisconnect(subscriber: () => void): () => void { + this.disconnectSubscribers.add(subscriber); + return () => this.disconnectSubscribers.delete(subscriber); } waitForEvent( @@ -488,6 +503,7 @@ export class BrowserReplCdpClient { title: t.title ?? '', url: t.url ?? '', attached: !!t.attached, + parentFrameId: t.parentFrameId, })); } diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 2a32797b..23f8728a 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -10,6 +10,7 @@ import { BrowserReplCdpClient } from './browser-cdp-client'; import { BrowserHelpers, buildBrowserGlobals } from './browser-helpers'; import { formatBrowserReplHelp } from './browser-repl-help'; import { CellRuntime } from './cell-runtime'; +import { CustomWebMCPRegistry } from './custom-webmcp'; import { createWebMCPClient } from './webmcp'; const SOCKET_PATH = process.env.BROWSER_REPL_SOCKET || '/tmp/browser-repl.sock'; @@ -376,11 +377,23 @@ async function evaluate(code: string): Promise { await cellRuntime.evaluate(code); } +const customToolRegistry = new CustomWebMCPRegistry(cdpClient, REPL_ID); +customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); +contextGlobal.customTools = Object.freeze({ + register: customToolRegistry.register, + remove: customToolRegistry.remove, + list: customToolRegistry.list, + get: customToolRegistry.get, +}); + // Request handling +type ExecuteOperation = 'execute' | 'custom_tools_get' | 'custom_tools_replace'; + interface ExecuteRequest { id: string; code: string; + operation?: ExecuteOperation; timeout_ms?: number; } @@ -394,6 +407,7 @@ interface ExecuteResponse { content_truncated: boolean; timed_out?: boolean; exiting?: boolean; + result?: unknown; duration_ms: number; } @@ -438,17 +452,27 @@ async function executeRequest( }, timeoutMs); if (typeof timer.unref === 'function') timer.unref(); }); - const evaluation = webmcpExecution.run( - executionAbortController.signal, - () => evaluate(request.code), - ); - await Promise.race([evaluation, timeoutPromise]); + const evaluation = webmcpExecution.run(executionAbortController.signal, async () => { + switch (request.operation ?? 'execute') { + case 'execute': + await evaluate(request.code); + return undefined; + case 'custom_tools_get': + return customToolRegistry.current(); + case 'custom_tools_replace': + return customToolRegistry.replace(request.code, evaluate); + default: + throw new Error(`unknown Browser REPL operation: ${request.operation}`); + } + }); + const result = await Promise.race([evaluation, timeoutPromise]); return { id: request.id, repl_id: REPL_ID, success: true, content: collector.items, content_truncated: collector.truncated, + result, duration_ms: Date.now() - start, }; } catch (err: any) { @@ -693,8 +717,17 @@ function onUncaughtException(err: unknown): void { setTimeout(() => process.exit(1), 100).unref(); } -function shutdown(signal: string): void { +let shuttingDown = false; + +async function shutdown(signal: string): Promise { + if (shuttingDown) return; + shuttingDown = true; process.stderr.write(`[browser-repl] received ${signal}, shutting down (repl_id=${REPL_ID})\n`); + try { + await customToolRegistry.dispose(); + } catch { + // The process is exiting; the next REPL also removes stale registrations. + } try { cdpClient.close(); } catch { @@ -719,8 +752,8 @@ async function main(): Promise { // ignore } - process.on('SIGTERM', () => shutdown('SIGTERM')); - process.on('SIGINT', () => shutdown('SIGINT')); + process.on('SIGTERM', () => void shutdown('SIGTERM')); + process.on('SIGINT', () => void shutdown('SIGINT')); process.on('unhandledRejection', onUnhandledRejection); process.on('uncaughtException', onUncaughtException); diff --git a/server/runtime/custom-webmcp.test.ts b/server/runtime/custom-webmcp.test.ts new file mode 100644 index 00000000..13c68d55 --- /dev/null +++ b/server/runtime/custom-webmcp.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { matchesURLPattern } from './custom-webmcp.ts'; + +test('matches URL patterns across top-level and nested frame URLs', () => { + assert.equal( + matchesURLPattern( + 'https://js.stripe.com/v3/checkout-inner-origin-frame-abc.html#state', + 'https://js.stripe.com/v3/checkout-inner-origin-frame-*', + ), + true, + ); + assert.equal( + matchesURLPattern( + 'https://assets.braintreegateway.com/web/3.120.0/html/hosted-fields-frame.html', + 'https://assets.braintreegateway.com/web/*', + ), + true, + ); + assert.equal( + matchesURLPattern( + 'https://example.com/v3/checkout-inner-origin-frame-abc.html', + 'https://js.stripe.com/v3/checkout-inner-origin-frame-*', + ), + false, + ); +}); + +test('supports wildcard schemes and hosts', () => { + assert.equal(matchesURLPattern('https://pay.example.com/form', '*://*.example.com/*'), true); + assert.equal(matchesURLPattern('http://pay.example.com/form', '*://*.example.com/*'), true); + assert.equal(matchesURLPattern('https://example.org/form', '*://*.example.com/*'), false); +}); diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts new file mode 100644 index 00000000..9e1090ac --- /dev/null +++ b/server/runtime/custom-webmcp.ts @@ -0,0 +1,748 @@ +import { ToolSchema } from '@modelcontextprotocol/core'; +import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv'; +import type { BrowserReplCdpClient, CdpEvent, CdpTarget } from './browser-cdp-client'; + +const WORLD_NAME = 'kernel-custom-webmcp'; +const BINDING_NAME = '__kernelCustomWebMCPInvoke'; +const PAGE_RUNTIME_KEY = '__kernelCustomWebMCP'; +const MAIN_PAGE_RUNTIME_KEY = '__kernelCustomWebMCPPage'; +const RECONNECT_DELAY_MS = 250; + +type JsonSchema = Record; +type Validator = ReturnType; + +type ToolKind = 'page' | 'cdp'; + +interface CustomToolMatch { + url_patterns: string[]; +} + +interface CustomToolMetadata { + name: string; + title?: string; + description: string; + inputSchema: JsonSchema; + annotations?: Record; +} + +interface CustomToolDefinitionInput { + id: string; + kind: ToolKind; + match: CustomToolMatch; + tool: CustomToolMetadata; + outputSchema?: JsonSchema; + execute: ( + input: Record, + context: CustomToolExecutionContext, + ) => unknown | Promise; +} + +interface CustomToolDefinition extends CustomToolDefinitionInput { + revision: number; + inputValidator: Validator; + outputValidator?: Validator; + pageExecuteSource?: string; +} + +export interface CustomToolFrameMatch { + frame_id: string; + target_id: string | null; + top_target_id: string; + url: string; +} + +export interface CustomToolExecutionContext { + signal: AbortSignal; + matches: CustomToolFrameMatch[]; +} + +interface CdpFrameTree { + frame: { + id: string; + loaderId?: string; + url?: string; + }; + childFrames?: CdpFrameTree[]; +} + +interface RuntimeEvaluateResult { + exceptionDetails?: { + exception?: {description?: string}; + text?: string; + }; + result?: {value?: unknown}; +} + +interface FrameInfo { + id: string; + url: string; + children?: FrameInfo[]; +} + +interface PageState { + targetId: string; + sessionId: string; + documentKey: string; + contextId: number | null; + signature: string; + matches: Map; + errors: string[]; +} + +interface ActiveInvocation { + controller: AbortController; +} + +export interface CustomToolsSnapshot { + repl_id: string; + revision: number; + source: string; + tools: Array<{ + id: string; + kind: ToolKind; + match: CustomToolMatch; + tool: CustomToolMetadata; + outputSchema?: JsonSchema; + revision: number; + }>; + installations: Array<{ + target_id: string; + definition_ids: string[]; + errors: string[]; + }>; +} + +function clone(value: T): T { + return structuredClone(value); +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function formatToolSchemaError(error: { issues: Array<{ path: PropertyKey[]; message: string }> }): string { + return error.issues + .map((issue) => `${issue.path.length ? issue.path.join('.') : 'tool'}: ${issue.message}`) + .join('; '); +} + +function compileSchema(schema: JsonSchema, field: string): Validator { + try { + return new AjvJsonSchemaValidator().getValidator(schema); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`${field} is not a compilable JSON Schema: ${message}`); + } +} + +function validatePattern(pattern: unknown): asserts pattern is string { + if (typeof pattern !== 'string' || !pattern.includes('://')) { + throw new Error('match.url_patterns entries must be URL patterns containing ://'); + } + const scheme = pattern.slice(0, pattern.indexOf('://')); + if (scheme !== 'http' && scheme !== 'https' && scheme !== '*') { + throw new Error(`unsupported URL pattern scheme: ${scheme}`); + } +} + +function patternRegexp(pattern: string): RegExp { + const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replaceAll('*', '.*'); + return new RegExp(`^${escaped}$`); +} + +function normalizedURL(url: string): string { + const hash = url.indexOf('#'); + return hash === -1 ? url : url.slice(0, hash); +} + +export function matchesURLPattern(url: string, pattern: string): boolean { + return patternRegexp(pattern).test(normalizedURL(url)); +} + +function flattenFrames(frame: FrameInfo): FrameInfo[] { + const frames = [frame]; + for (const child of frame.children ?? []) frames.push(...flattenFrames(child)); + return frames; +} + +function definitionPublic(definition: CustomToolDefinition) { + const result: CustomToolsSnapshot['tools'][number] = { + id: definition.id, + kind: definition.kind, + match: clone(definition.match), + tool: clone(definition.tool), + revision: definition.revision, + }; + if (definition.outputSchema) result.outputSchema = clone(definition.outputSchema); + return result; +} + +export class CustomWebMCPRegistry { + private definitions = new Map(); + private source = ''; + private revision = 0; + private staging: Map | null = null; + private pages = new Map(); + private sessions = new Map(); + private activeInvocations = new Map(); + private reconciliation: Promise | null = null; + private reconcileAgain = false; + private reconnectTimer: ReturnType | null = null; + private disposed = false; + private readonly unsubscribeEvent: () => void; + private readonly unsubscribeDisconnect: () => void; + private onError?: (message: string) => void; + private readonly client: BrowserReplCdpClient; + private readonly replID: string; + + constructor(client: BrowserReplCdpClient, replID: string) { + this.client = client; + this.replID = replID; + this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); + this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); + this.scheduleReconcile(); + } + + setErrorHandler(handler: (message: string) => void): void { + this.onError = handler; + } + + register = (input: CustomToolDefinitionInput): ReturnType => { + const destination = this.staging ?? this.definitions; + const prior = destination.get(input.id) ?? (this.staging ? this.definitions.get(input.id) : undefined); + const definition = this.validateDefinition(input, prior?.revision ?? 0); + for (const existing of destination.values()) { + if (existing.id !== definition.id && existing.tool.name === definition.tool.name) { + throw new Error(`tool name already registered: ${definition.tool.name}`); + } + } + destination.set(definition.id, definition); + if (!this.staging) { + this.revision++; + this.source = ''; + this.scheduleReconcile(); + } + return definitionPublic(definition); + }; + + remove = (id: string): boolean => { + const destination = this.staging ?? this.definitions; + const removed = destination.delete(id); + if (removed && !this.staging) { + this.revision++; + this.source = ''; + this.scheduleReconcile(); + } + return removed; + }; + + list = (): CustomToolsSnapshot['tools'] => { + return [...this.definitions.values()] + .map(definitionPublic) + .sort((a, b) => a.id.localeCompare(b.id)); + }; + + get = (id: string): CustomToolsSnapshot['tools'][number] | null => { + const definition = this.definitions.get(id); + return definition ? definitionPublic(definition) : null; + }; + + async replace(source: string, evaluate: (source: string) => Promise): Promise { + if (this.staging) throw new Error('custom tool replacement is already in progress'); + this.staging = new Map(); + try { + await evaluate(`await (async () => {\n${source}\n})()`); + this.definitions = this.staging; + this.source = source; + this.revision++; + } finally { + this.staging = null; + } + try { + await this.reconcile(); + } catch (error) { + this.reportError(error); + this.retryReconcile(); + } + return this.snapshot(); + } + + async current(): Promise { + try { + await this.reconcile(); + } catch (error) { + this.reportError(error); + this.retryReconcile(); + } + return this.snapshot(); + } + + snapshot(): CustomToolsSnapshot { + return { + repl_id: this.replID, + revision: this.revision, + source: this.source, + tools: this.list(), + installations: [...this.pages.values()] + .map((page) => ({ + target_id: page.targetId, + definition_ids: [...page.matches.keys()].sort(), + errors: [...page.errors], + })) + .sort((a, b) => a.target_id.localeCompare(b.target_id)), + }; + } + + async dispose(): Promise { + this.disposed = true; + this.unsubscribeEvent(); + this.unsubscribeDisconnect(); + if (this.reconnectTimer) clearTimeout(this.reconnectTimer); + if (this.reconciliation) await this.reconciliation.catch(() => undefined); + for (const invocation of this.activeInvocations.values()) invocation.controller.abort(); + this.activeInvocations.clear(); + await Promise.all([...this.pages.values()].map(async (page) => { + await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + await this.detachPage(page); + })); + this.pages.clear(); + this.sessions.clear(); + } + + private validateDefinition(input: CustomToolDefinitionInput, previousRevision: number): CustomToolDefinition { + if (!input || typeof input !== 'object') throw new Error('definition must be an object'); + if (typeof input.id !== 'string' || input.id.trim() === '') { + throw new Error('definition.id must be a non-empty string'); + } + if (input.kind !== 'page' && input.kind !== 'cdp') { + throw new Error('definition.kind must be page or cdp'); + } + if (!input.match || !Array.isArray(input.match.url_patterns) || input.match.url_patterns.length === 0) { + throw new Error('definition.match.url_patterns must be a non-empty array'); + } + for (const pattern of input.match.url_patterns) validatePattern(pattern); + if (typeof input.execute !== 'function') throw new Error('definition.execute must be a function'); + + const toolResult = ToolSchema.safeParse({ ...input.tool, outputSchema: input.outputSchema }); + if (!toolResult.success) { + throw new Error(`invalid MCP tool definition: ${formatToolSchemaError(toolResult.error)}`); + } + + const inputValidator = compileSchema(input.tool.inputSchema, 'tool.inputSchema'); + const outputValidator = input.outputSchema + ? compileSchema(input.outputSchema, 'outputSchema') + : undefined; + const pageExecuteSource = input.kind === 'page' ? input.execute.toString() : undefined; + if (pageExecuteSource) { + try { + new Function(`return (${pageExecuteSource})`); + } catch (error) { + throw new Error(`definition.execute cannot be installed in the page: ${String(error)}`); + } + } + + return { + id: input.id, + kind: input.kind, + match: clone(input.match), + tool: clone(input.tool), + outputSchema: input.outputSchema ? clone(input.outputSchema) : undefined, + execute: input.execute, + revision: previousRevision + 1, + inputValidator, + outputValidator, + pageExecuteSource, + }; + } + + private handleEvent(event: CdpEvent): void { + if (event.method === 'Runtime.bindingCalled') { + void this.handleBinding(event); + return; + } + if (event.method === 'Target.detachedFromTarget') { + const sessionId = (event.params as {sessionId?: string})?.sessionId; + const targetId = sessionId ? this.sessions.get(sessionId) : undefined; + if (sessionId && targetId) { + this.sessions.delete(sessionId); + this.pages.delete(targetId); + } + } + if ( + event.method === 'Target.targetCreated' || + event.method === 'Target.targetInfoChanged' || + event.method === 'Target.targetDestroyed' || + event.method === 'Target.detachedFromTarget' || + event.method === 'Page.frameAttached' || + event.method === 'Page.frameNavigated' || + event.method === 'Page.frameDetached' + ) { + this.scheduleReconcile(); + } + } + + private handleDisconnect(): void { + this.pages.clear(); + this.sessions.clear(); + if (this.disposed || this.reconnectTimer) return; + this.retryReconcile(); + } + + private scheduleReconcile(): void { + queueMicrotask(() => void this.reconcile().catch((error) => { + this.reportError(error); + this.retryReconcile(); + })); + } + + private retryReconcile(): void { + if (this.disposed || this.reconnectTimer) return; + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null; + this.scheduleReconcile(); + }, RECONNECT_DELAY_MS); + this.reconnectTimer.unref?.(); + } + + private async reconcile(): Promise { + if (this.disposed) return; + if (this.reconciliation) { + this.reconcileAgain = true; + return this.reconciliation; + } + this.reconciliation = (async () => { + do { + this.reconcileAgain = false; + await this.reconcileOnce(); + } while (this.reconcileAgain && !this.disposed); + })(); + try { + await this.reconciliation; + } finally { + this.reconciliation = null; + } + } + + private async reconcileOnce(): Promise { + await this.client.ensureConnected(); + await this.client.browserCommand('Target.setDiscoverTargets', { discover: true }); + const targets = await this.client.listTargets(); + const pages = targets.filter( + (target) => target.type === 'page' && (target.url.startsWith('https://') || target.url.startsWith('http://')), + ); + const liveTargets = new Set(pages.map((target) => target.targetId)); + + for (const [targetId, page] of this.pages) { + if (!liveTargets.has(targetId)) { + await this.detachPage(page); + this.pages.delete(targetId); + this.sessions.delete(page.sessionId); + } + } + + for (const target of pages) { + try { + await this.reconcilePage(target, targets); + } catch (error) { + const page = this.pages.get(target.targetId); + if (page) page.errors = [error instanceof Error ? error.message : String(error)]; + else this.reportError(error); + } + } + } + + private async reconcilePage(target: CdpTarget, targets: CdpTarget[]): Promise { + let page = this.pages.get(target.targetId); + if (!page) { + const attached = await this.client.browserCommand<{ sessionId: string }>('Target.attachToTarget', { + targetId: target.targetId, + flatten: true, + }); + page = { + targetId: target.targetId, + sessionId: attached.sessionId, + documentKey: '', + contextId: null, + signature: '', + matches: new Map(), + errors: [], + }; + this.pages.set(target.targetId, page); + this.sessions.set(page.sessionId, target.targetId); + await this.client.send('Page.enable', undefined, page.sessionId); + await this.client.send('Runtime.enable', undefined, page.sessionId); + } + + const result = await this.client.send<{frameTree: CdpFrameTree}>('Page.getFrameTree', undefined, page.sessionId); + const root = this.decodeFrameTree(result.frameTree); + const frames = flattenFrames(root); + const knownFrameIDs = new Set(frames.map((frame) => frame.id)); + const iframeTargetList = targets.filter((candidate) => candidate.type === 'iframe'); + let added = true; + while (added) { + added = false; + for (const iframe of iframeTargetList) { + if (knownFrameIDs.has(iframe.targetId) || !iframe.parentFrameId || !knownFrameIDs.has(iframe.parentFrameId)) continue; + frames.push({id: iframe.targetId, url: iframe.url}); + knownFrameIDs.add(iframe.targetId); + added = true; + } + } + const iframeTargets = new Set(iframeTargetList.map((candidate) => candidate.targetId)); + const matches = new Map(); + for (const definition of this.definitions.values()) { + const matchingFrames = frames + .filter((frame) => definition.match.url_patterns.some((pattern) => matchesURLPattern(frame.url, pattern))) + .map((frame) => ({ + frame_id: frame.id, + target_id: iframeTargets.has(frame.id) ? frame.id : frame.id === root.id ? target.targetId : null, + top_target_id: target.targetId, + url: normalizedURL(frame.url), + })); + if (matchingFrames.length) matches.set(definition.id, matchingFrames); + } + + const documentKey = `${root.id}:${result.frameTree.frame.loaderId ?? ''}`; + const signature = JSON.stringify([...matches.keys()].sort().map((id) => [id, this.definitions.get(id)!.revision])); + const registrationCurrent = page.documentKey === documentKey && page.signature === signature && page.contextId !== null; + page.documentKey = documentKey; + page.matches = matches; + if (registrationCurrent) return; + + const world = await this.client.send<{ executionContextId: number }>( + 'Page.createIsolatedWorld', + { frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false }, + page.sessionId, + ); + await this.client.send( + 'Runtime.addBinding', + { name: BINDING_NAME, executionContextName: WORLD_NAME }, + page.sessionId, + ); + page.contextId = world.executionContextId; + await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, world.executionContextId); + + const definitions = [...page.matches.keys()].map((id) => this.definitions.get(id)!); + const pageErrors = await this.installPageDefinitions( + page, + definitions.filter((definition) => definition.kind === 'page'), + MAIN_PAGE_RUNTIME_KEY, + ); + const cdpErrors = await this.installPageDefinitions( + page, + definitions.filter((definition) => definition.kind === 'cdp'), + PAGE_RUNTIME_KEY, + world.executionContextId, + ); + page.errors = [...pageErrors, ...cdpErrors]; + page.signature = signature; + } + + private decodeFrameTree(tree: CdpFrameTree): FrameInfo { + return { + id: tree.frame.id, + url: tree.frame.url ?? '', + children: (tree.childFrames ?? []).map((child) => this.decodeFrameTree(child)), + }; + } + + private async disposePageRuntime(page: PageState, runtimeKey: string, contextId?: number): Promise { + const params: Record = { + expression: `globalThis[${JSON.stringify(runtimeKey)}]?.dispose?.()`, + awaitPromise: true, + }; + if (contextId !== undefined) params.contextId = contextId; + try { + await this.client.send('Runtime.evaluate', params, page.sessionId, 1_000); + } catch { + // A navigation may already have destroyed this runtime. + } + } + + private async installPageDefinitions( + page: PageState, + definitions: CustomToolDefinition[], + runtimeKey: string, + contextId?: number, + ): Promise { + const serialized = definitions.map((definition) => ({ + id: definition.id, + kind: definition.kind, + tool: definition.tool, + revision: definition.revision, + pageExecuteSource: definition.pageExecuteSource, + })); + const expression = ` + (async () => { + const key = ${JSON.stringify(runtimeKey)}; + globalThis[key]?.dispose?.(); + const definitions = ${JSON.stringify(serialized)}; + const controllers = new Map(); + const pending = new Map(); + const errors = []; + const resolveInvocation = (id, value) => { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.resolve(value); + }; + const rejectInvocation = (id, message) => { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.reject(new Error(message)); + }; + globalThis[key] = { + resolveInvocation, + rejectInvocation, + dispose() { + for (const controller of controllers.values()) controller.abort(); + controllers.clear(); + }, + }; + for (const definition of definitions) { + const controller = new AbortController(); + controllers.set(definition.id, controller); + let execute; + if (definition.kind === 'page') { + execute = (0, eval)(\`(\${definition.pageExecuteSource})\`); + } else { + execute = (input, {signal} = {}) => { + const invocationId = crypto.randomUUID(); + return new Promise((resolve, reject) => { + pending.set(invocationId, {resolve, reject}); + signal?.addEventListener('abort', () => { + if (!pending.has(invocationId)) return; + pending.delete(invocationId); + globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ + type: 'cancel', + invocation_id: invocationId, + })); + reject(new DOMException('Canceled', 'AbortError')); + }, {once: true}); + globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ + type: 'invoke', + invocation_id: invocationId, + definition_id: definition.id, + revision: definition.revision, + input, + })); + }); + }; + } + try { + await document.modelContext.registerTool({...definition.tool, execute}, {signal: controller.signal}); + } catch (error) { + controller.abort(); + controllers.delete(definition.id); + errors.push(\`\${definition.id}: \${error instanceof Error ? error.message : String(error)}\`); + } + } + return errors; + })() + `; + const params: Record = { expression, awaitPromise: true, returnByValue: true }; + if (contextId !== undefined) params.contextId = contextId; + const result = await this.client.send('Runtime.evaluate', params, page.sessionId); + if (result.exceptionDetails) { + throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? 'tool installation failed'); + } + return Array.isArray(result.result?.value) ? result.result.value : []; + } + + private async handleBinding(event: CdpEvent): Promise { + const params = event.params as { name?: string; payload?: string; executionContextId?: number }; + if (params.name !== BINDING_NAME || !event.sessionId || params.executionContextId === undefined) return; + let payload: unknown; + try { + payload = JSON.parse(params.payload ?? ''); + } catch { + return; + } + if (!isRecord(payload)) return; + if (payload.type === 'cancel' && typeof payload.invocation_id === 'string') { + this.activeInvocations.get(payload.invocation_id)?.controller.abort(); + return; + } + if ( + payload.type !== 'invoke' || + typeof payload.invocation_id !== 'string' || + typeof payload.definition_id !== 'string' || + typeof payload.revision !== 'number' || + !payload.input || + typeof payload.input !== 'object' || + Array.isArray(payload.input) + ) { + return; + } + + const targetId = this.sessions.get(event.sessionId); + const page = targetId ? this.pages.get(targetId) : undefined; + const definition = this.definitions.get(payload.definition_id); + if (!targetId || !page || !definition || definition.revision !== payload.revision) { + await this.respondToInvocation(event.sessionId, params.executionContextId, payload.invocation_id, undefined, 'custom tool is no longer registered'); + return; + } + + const controller = new AbortController(); + this.activeInvocations.set(payload.invocation_id, {controller}); + try { + const inputResult = definition.inputValidator(payload.input); + if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); + const output = await definition.execute(inputResult.data as Record, { + signal: controller.signal, + matches: clone(page.matches.get(definition.id) ?? []), + }); + if (definition.outputValidator) { + const outputResult = definition.outputValidator(output); + if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); + } + await this.respondToInvocation(event.sessionId, params.executionContextId, payload.invocation_id, output); + } catch (error) { + await this.respondToInvocation( + event.sessionId, + params.executionContextId, + payload.invocation_id, + undefined, + error instanceof Error ? error.message : String(error), + ); + } finally { + this.activeInvocations.delete(payload.invocation_id); + } + } + + private async respondToInvocation( + sessionId: string, + contextId: number, + invocationId: string, + output?: unknown, + error?: string, + ): Promise { + let expression: string; + if (error !== undefined) { + expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.rejectInvocation(${JSON.stringify(invocationId)}, ${JSON.stringify(error)})`; + } else { + const serialized = JSON.stringify(output ?? null); + expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.resolveInvocation(${JSON.stringify(invocationId)}, ${serialized})`; + } + try { + await this.client.send('Runtime.evaluate', { expression, contextId, awaitPromise: true }, sessionId); + } catch { + // The registration document may have navigated after starting the tool. + } + } + + private async detachPage(page: PageState): Promise { + try { + await this.client.browserCommand('Target.detachFromTarget', { sessionId: page.sessionId }); + } catch { + // The target or connection is already gone. + } + } + + private reportError(error: unknown): void { + this.onError?.(error instanceof Error ? error.message : String(error)); + } +} diff --git a/server/runtime/package-lock.json b/server/runtime/package-lock.json index fb2c4843..54bba6db 100644 --- a/server/runtime/package-lock.json +++ b/server/runtime/package-lock.json @@ -6,6 +6,8 @@ "": { "name": "@kernel/browser-repl-runtime", "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "@modelcontextprotocol/server": "2.0.0", "meriyah": "7.3.1", "patchright": "1.62.3", "playwright-core": "1.62.1", @@ -526,6 +528,31 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", + "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", + "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@types/node": { "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", @@ -698,6 +725,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/server/runtime/package.json b/server/runtime/package.json index 80fbe73b..70ce00df 100644 --- a/server/runtime/package.json +++ b/server/runtime/package.json @@ -5,6 +5,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@modelcontextprotocol/core": "2.0.0", + "@modelcontextprotocol/server": "2.0.0", "meriyah": "7.3.1", "patchright": "1.62.3", "playwright-core": "1.62.1", From c02f799896ffa415a0f96625a85007c9b52c2ff5 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:43:56 +0000 Subject: [PATCH 02/18] Fix custom WebMCP review findings --- server/cmd/api/api/custom_webmcp.go | 4 +- server/cmd/api/api/custom_webmcp_test.go | 9 + server/docs/repl.md | 11 +- server/lib/oapi/oapi.go | 1299 +++++++++++----------- server/openapi.yaml | 6 +- server/runtime/browser-cdp-client.ts | 4 +- server/runtime/browser-repl-help.ts | 25 +- server/runtime/browser-repl.ts | 6 +- server/runtime/custom-webmcp.test.ts | 7 +- server/runtime/custom-webmcp.ts | 346 ++++-- server/scripts/replhelp/main.mjs | 2 + 11 files changed, 969 insertions(+), 750 deletions(-) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index 6c99de1c..c5374183 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -23,6 +23,7 @@ type customWebMCPRuntimeSnapshot struct { ReplID string `json:"repl_id"` Revision int `json:"revision"` Source string `json:"source"` + SourceDirty bool `json:"source_dirty"` Tools []customWebMCPRuntimeDefinition `json:"tools"` Installations []oapi.CustomWebMCPInstallation `json:"installations"` } @@ -90,7 +91,7 @@ func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operatio request, err := prepareBrowserReplOperation(source, operation, customWebMCPOperationTimeout) if err != nil { - return oapi.CustomWebMCPRegistry{}, err + return oapi.CustomWebMCPRegistry{}, &customWebMCPExecutionError{message: err.Error()} } if err := m.ensureLocked(ctx); err != nil { return oapi.CustomWebMCPRegistry{}, fmt.Errorf("start Browser REPL: %w", err) @@ -152,6 +153,7 @@ func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operatio ReplId: runtime.ReplID, Revision: runtime.Revision, Source: runtime.Source, + SourceDirty: runtime.SourceDirty, Tools: tools, Installations: runtime.Installations, }, nil diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 7d0f5eaf..f99cd047 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -41,6 +41,7 @@ func TestCustomWebMCPRegistryUsesBrowserReplLifecycle(t *testing.T) { require.NotEmpty(t, body.ReplId) require.Equal(t, 1, body.Revision) require.Equal(t, customWebMCPTestSource, body.Source) + require.False(t, body.SourceDirty) require.Len(t, body.Tools, 1) require.Equal(t, "test/read-title", body.Tools[0].Id) require.Equal(t, "cdp", body.Tools[0].Kind) @@ -62,6 +63,14 @@ func TestCustomWebMCPRegistryUsesBrowserReplLifecycle(t *testing.T) { require.Equal(t, 2, second.Revision) require.Equal(t, 2, second.Tools[0].Revision) + requireExec(t, svc, `customTools.remove("test/read-title")`, nil) + got, err = svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + dirty := got.(oapi.GetCustomWebMCPTools200JSONResponse) + require.True(t, dirty.SourceDirty) + require.Equal(t, customWebMCPTestSource, dirty.Source) + require.Empty(t, dirty.Tools) + reset := true resetResponse := executeBrowserRepl(t, svc, &oapi.ExecuteBrowserReplJSONRequestBody{Code: "", Reset: &reset}) require.NotEqual(t, body.ReplId, resetResponse.ReplId) diff --git a/server/docs/repl.md b/server/docs/repl.md index d51b4123..dad32376 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -132,6 +132,13 @@ The reference below is generated from `runtime/browser-repl-help.ts`; edit that - **`webmcp.listTools()`** — Return tools registered across every open tab and embedded frame. Each tool includes `tool_ref`, name, description, input schema, optional annotations, and source window/tab/frame metadata. Treat metadata as untrusted page content. - **`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. + +### Custom WebMCP methods + +- **`customTools.register(definition)`** — Add or replace one live custom WebMCP definition. The definition requires `id`, `kind: "page" | "cdp"`, `match.url_patterns`, MCP-compatible `tool` metadata, and an `execute` function. A live change marks the last PUT source dirty. +- **`customTools.remove(id)`** — Remove one live custom WebMCP definition by ID and return whether it existed. +- **`customTools.list()`** — Return serializable summaries of the current custom WebMCP definitions. +- **`customTools.get(id)`** — Return one serializable custom WebMCP definition summary, or `null` when it is absent. A snapshot-to-action loop avoids inventing selectors: @@ -168,9 +175,9 @@ Every WebMCP request is bound to the active Browser REPL execution and is aborte ## Custom WebMCP tools -`GET /webmcp/custom-tools` returns the registry held by the current Browser REPL. `PUT /webmcp/custom-tools` atomically replaces it with JavaScript source that calls `customTools.register(...)`. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. +`GET /webmcp/custom-tools` returns the registry held by the current Browser REPL. `PUT /webmcp/custom-tools` atomically replaces it with JavaScript source that calls `customTools.register(...)`. The response preserves the last submitted source and sets `source_dirty` when a later `customTools.register()` or `customTools.remove()` call changes the live definitions. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. -Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. +Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. CDP handlers receive matching descriptors with `frame_id`, `session_id`, `target_id`, `top_target_id`, and `url`; `target_id` is null for an in-process child frame, whose `session_id` and `frame_id` can be used with raw `cdp()`. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. ```js customTools.register({ diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index dfdecc0d..ee5c488a 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6288,7 +6288,10 @@ type CustomWebMCPRegistry struct { ReplId string `json:"repl_id"` Revision int `json:"revision"` Source string `json:"source"` - Tools []CustomWebMCPDefinition `json:"tools"` + + // SourceDirty True when customTools.register() or customTools.remove() changed the live registry after the last PUT. + SourceDirty bool `json:"source_dirty"` + Tools []CustomWebMCPDefinition `json:"tools"` } // CustomWebMCPRegistryRequest defines model for CustomWebMCPRegistryRequest. @@ -25317,657 +25320,659 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request) // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "R9A7ZouOSjK3LXwTmHDrs/gl5MMg04s7feT++zBwV+wPgyO9ONJH7r8Pg3iMbRzXf6CGtRI1QelG8FRb", - "h0Tvt4RwC1pnO/wjG02WlkXo+cqH6MLPQ/IcrithGZyZHtG5IdKawk2xMVkS8KBxhh7oXeiEYdgdiaFe", - "1TU10WOrjs/dHf0oOCKxrD8e3vcsq6nue6i7YUn8MdHnTVoWrPlyePru5cn/z977L7eRI+mir4Jl3IiW", - "dklKdrt7Z+zo2Ctbcre2LVshyTO7PewQwSqQRKsI1BRQkuk+3jgPcZ7wPMkJZCZQP1hFFimpbc/u/NEj", - "SwUUCshMJBKZ33d10uv3/npxCv9/fPLmBH64OHl7dHbSGXKz1QX+Wek7tZIN2by+x82UH4FoOySjUcWJ", - "oAsW8gSQoAaxlAuIDh5AfnjCLP+glV5A/QZ1gwCD5fxwLCghSIVxzC3HJHOdLWCz0yqsNXilbigTkeg7", - "tod5ATgkTBig9NVx+zyM+ywTM57FkHoJSZqapfkkkYDdJu2QveJJIrJB8UuaAMhifXd5xQ7C6A/oTx55", - "LMA8+bQ9YOMBaCYjBBvXxhIiHHcyE8zMeSqA6kvGgfUygsF4/I5yOa80YYI9OEpEFOHfGA9b7fPIwOte", - "x9kSKE/XZ11WiID7vj79Opq7YaqZuPYu5fryE2z2yrdCP7jeKZSkb9XhpWuxrjOIajlV6tzbJbYI3RW1", - "UJ1LbKptoZikS1t4sNzWLV/X5sfh2dADVowQYd+GDvDZEhtn0T7Rs26t3+iZb1uqSsG0sA09nBbPQ4pM", - "Uz+QpNK1l5/FsqkPzMu4NsLaZKNUlLrDJJZLbOY7TuStuL6V4q7jIr+Rt+IvUtzVVrropvN6+55WF50K", - "bUpdbfzMM2xyXGpR700qGUjkOnV2qiQRrtW7CujtW/V34Vtt6HTr/lb7Kldwd+mqKOrzPVGF1rWMk02k", - "WERLdhonot7aWX+pZt2mifp5g22qk+Q7zHwAq0tPFLda7QPBCrp2gk/7XlI+K/CYN7Cy8lkVNpxaxxpY", - "DYEAws1Qp46O9YJyL99Ak0qPiHDQ0SS43t5Ag5otqHQ1l1PbvSP3dLWbKO3SOkqrrTTvNHrNq4MuMB86", - "tH4bHm7pY4t5LPpqmkvLJ9c6FapTV1d88g6eDV2sELtvz5sfWDq7bKB1Muv+Cv741tjuvf4KZOq2aLQP", - "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", + "bKMDAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index cc70b856..9425c3a3 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1521,6 +1521,7 @@ paths: - REPL: `repl.help`, `repl.write`, `repl.emitImage` - Browser control: `cdp`, `drainEvents`, `waitForEvent`, `gotoUrl`, `pageInfo`, `accessibilitySnapshot`, `click`, `typeText`, `fillInput`, `pressKey`, `scroll`, `captureScreenshot`, `listTabs`, `currentTab`, `switchTab`, `newTab`, `closeTab`, `ensureRealTab`, `iframeTarget`, `waitMs`, `waitForLoad`, `waitForElement`, `waitForNetworkIdle`, `js`, `uploadFile`, `httpGet` - WebMCP: `webmcp.listTools`, `webmcp.invokeTool` + - Custom WebMCP: `customTools.register`, `customTools.remove`, `customTools.list`, `customTools.get` Browser-control methods are available both directly and on `browser`. The same frozen @@ -6511,7 +6512,7 @@ components: additionalProperties: false CustomWebMCPRegistry: type: object - required: [repl_id, revision, source, tools, installations] + required: [repl_id, revision, source, source_dirty, tools, installations] properties: repl_id: type: string @@ -6519,6 +6520,9 @@ components: type: integer source: type: string + source_dirty: + type: boolean + description: True when customTools.register() or customTools.remove() changed the live registry after the last PUT. tools: type: array items: diff --git a/server/runtime/browser-cdp-client.ts b/server/runtime/browser-cdp-client.ts index 787f7277..84000515 100644 --- a/server/runtime/browser-cdp-client.ts +++ b/server/runtime/browser-cdp-client.ts @@ -494,9 +494,9 @@ export class BrowserReplCdpClient { return { timeout, clampedByDeadline: false }; } - async listTargets(): Promise { + async listTargets(timeoutMs?: number): Promise { await this.ensureConnected(); - const res = await this.browserCommand<{ targetInfos: any[] }>('Target.getTargets'); + const res = await this.send<{targetInfos: any[]}>('Target.getTargets', undefined, undefined, timeoutMs); return (res.targetInfos ?? []).map((t) => ({ targetId: t.targetId, type: t.type, diff --git a/server/runtime/browser-repl-help.ts b/server/runtime/browser-repl-help.ts index 472ef796..134fe691 100644 --- a/server/runtime/browser-repl-help.ts +++ b/server/runtime/browser-repl-help.ts @@ -7,7 +7,7 @@ export interface BrowserReplHelpEntry { example?: string; } -export type BrowserReplHelpGroup = 'repl' | 'browser' | 'webmcp'; +export type BrowserReplHelpGroup = 'repl' | 'browser' | 'webmcp' | 'customTools'; export const browserReplHelpRegistry = { repl: { @@ -173,6 +173,25 @@ export const browserReplHelpRegistry = { 'Fetch a URL from the VM and return its response body as text. Supports custom headers and `timeoutSec` (default `20`); non-2xx responses throw. Its timeout covers body consumption and is clamped below the active execution deadline.', }, }, + customTools: { + register: { + signature: 'customTools.register(definition)', + description: + 'Add or replace one live custom WebMCP definition. The definition requires `id`, `kind: "page" | "cdp"`, `match.url_patterns`, MCP-compatible `tool` metadata, and an `execute` function. A live change marks the last PUT source dirty.', + }, + remove: { + signature: 'customTools.remove(id)', + description: 'Remove one live custom WebMCP definition by ID and return whether it existed.', + }, + list: { + signature: 'customTools.list()', + description: 'Return serializable summaries of the current custom WebMCP definitions.', + }, + get: { + signature: 'customTools.get(id)', + description: 'Return one serializable custom WebMCP definition summary, or `null` when it is absent.', + }, + }, webmcp: { listTools: { signature: 'webmcp.listTools()', @@ -204,11 +223,12 @@ const groupPrefix: Record = { repl: 'repl.', browser: '', webmcp: 'webmcp.', + customTools: 'customTools.', }; export function listBrowserReplHelpEntries(): NamedBrowserReplHelpEntry[] { const entries: NamedBrowserReplHelpEntry[] = []; - for (const group of ['repl', 'browser', 'webmcp'] as const) { + for (const group of ['repl', 'browser', 'webmcp', 'customTools'] as const) { for (const [method, entry] of Object.entries(browserReplHelpRegistry[group])) { entries.push({ ...entry, @@ -235,6 +255,7 @@ function helpIndex(): string { `REPL: ${names('repl')}`, `Browser control: ${names('browser')}`, `WebMCP: ${names('webmcp')}`, + `Custom WebMCP: ${names('customTools')}`, ].join('\n'); } diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 23f8728a..581afdd2 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -377,7 +377,11 @@ async function evaluate(code: string): Promise { await cellRuntime.evaluate(code); } -const customToolRegistry = new CustomWebMCPRegistry(cdpClient, REPL_ID); +const customToolRegistry = new CustomWebMCPRegistry( + cdpClient, + REPL_ID, + (signal, callback) => webmcpExecution.run(signal, callback), +); customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); contextGlobal.customTools = Object.freeze({ register: customToolRegistry.register, diff --git a/server/runtime/custom-webmcp.test.ts b/server/runtime/custom-webmcp.test.ts index 13c68d55..e5fca7c8 100644 --- a/server/runtime/custom-webmcp.test.ts +++ b/server/runtime/custom-webmcp.test.ts @@ -26,8 +26,11 @@ test('matches URL patterns across top-level and nested frame URLs', () => { ); }); -test('supports wildcard schemes and hosts', () => { +test('supports wildcard schemes and host boundaries', () => { assert.equal(matchesURLPattern('https://pay.example.com/form', '*://*.example.com/*'), true); - assert.equal(matchesURLPattern('http://pay.example.com/form', '*://*.example.com/*'), true); + assert.equal(matchesURLPattern('http://example.com/form', '*://*.example.com/*'), true); assert.equal(matchesURLPattern('https://example.org/form', '*://*.example.com/*'), false); + assert.equal(matchesURLPattern('https://evil.com/.example.com/form', '*://*.example.com/*'), false); + assert.equal(matchesURLPattern('https://evil.com/form?next=.example.com/path', '*://*.example.com/*'), false); + assert.equal(matchesURLPattern('chrome-extension://abc/.example.com/form', '*://*.example.com/*'), false); }); diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index 9e1090ac..d4bdd8a7 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -7,6 +7,8 @@ const BINDING_NAME = '__kernelCustomWebMCPInvoke'; const PAGE_RUNTIME_KEY = '__kernelCustomWebMCP'; const MAIN_PAGE_RUNTIME_KEY = '__kernelCustomWebMCPPage'; const RECONNECT_DELAY_MS = 250; +const RECONCILE_COMMAND_TIMEOUT_MS = 5_000; +const MAX_OUTPUT_BYTES = 1 << 20; type JsonSchema = Record; type Validator = ReturnType; @@ -46,6 +48,7 @@ interface CustomToolDefinition extends CustomToolDefinitionInput { export interface CustomToolFrameMatch { frame_id: string; + session_id: string; target_id: string | null; top_target_id: string; url: string; @@ -75,10 +78,17 @@ interface RuntimeEvaluateResult { interface FrameInfo { id: string; + sessionId: string; + targetId: string | null; url: string; children?: FrameInfo[]; } +interface IframeSession { + targetId: string; + sessionId: string; +} + interface PageState { targetId: string; sessionId: string; @@ -97,6 +107,7 @@ export interface CustomToolsSnapshot { repl_id: string; revision: number; source: string; + source_dirty: boolean; tools: Array<{ id: string; kind: ToolKind; @@ -135,28 +146,68 @@ function compileSchema(schema: JsonSchema, field: string): Validator { } } -function validatePattern(pattern: unknown): asserts pattern is string { - if (typeof pattern !== 'string' || !pattern.includes('://')) { - throw new Error('match.url_patterns entries must be URL patterns containing ://'); - } - const scheme = pattern.slice(0, pattern.indexOf('://')); - if (scheme !== 'http' && scheme !== 'https' && scheme !== '*') { - throw new Error(`unsupported URL pattern scheme: ${scheme}`); - } +interface ParsedURLPattern { + scheme: 'http' | 'https' | '*'; + host: string; + path: RegExp; } -function patternRegexp(pattern: string): RegExp { +function wildcardRegexp(pattern: string): RegExp { const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replaceAll('*', '.*'); return new RegExp(`^${escaped}$`); } +function parseURLPattern(pattern: unknown): ParsedURLPattern { + if (typeof pattern !== 'string') { + throw new Error('match.url_patterns entries must be strings'); + } + const separator = pattern.indexOf('://'); + if (separator === -1) { + throw new Error('match.url_patterns entries must contain ://'); + } + const scheme = pattern.slice(0, separator); + if (scheme !== 'http' && scheme !== 'https' && scheme !== '*') { + throw new Error(`unsupported URL pattern scheme: ${scheme}`); + } + const remainder = pattern.slice(separator + 3); + const pathStart = remainder.indexOf('/'); + if (pathStart === -1) { + throw new Error('match.url_patterns entries must include a path'); + } + const host = remainder.slice(0, pathStart).toLowerCase(); + if (!host || (host.includes('*') && host !== '*' && !host.startsWith('*.')) || host.slice(2).includes('*')) { + throw new Error(`unsupported URL pattern host: ${host}`); + } + return {scheme, host, path: wildcardRegexp(remainder.slice(pathStart))}; +} + function normalizedURL(url: string): string { const hash = url.indexOf('#'); return hash === -1 ? url : url.slice(0, hash); } export function matchesURLPattern(url: string, pattern: string): boolean { - return patternRegexp(pattern).test(normalizedURL(url)); + const parsed = parseURLPattern(pattern); + let candidate: URL; + try { + candidate = new URL(normalizedURL(url)); + } catch { + return false; + } + const scheme = candidate.protocol.slice(0, -1); + if (parsed.scheme === '*' ? scheme !== 'http' && scheme !== 'https' : scheme !== parsed.scheme) return false; + + const host = candidate.host.toLowerCase(); + let hostMatches: boolean; + if (parsed.host === '*') { + hostMatches = true; + } else if (parsed.host.startsWith('*.')) { + const suffix = parsed.host.slice(2); + hostMatches = host === suffix || host.endsWith(`.${suffix}`); + } else { + hostMatches = host === parsed.host; + } + return hostMatches && parsed.path.test(`${candidate.pathname}${candidate.search}`); } function flattenFrames(frame: FrameInfo): FrameInfo[] { @@ -180,10 +231,13 @@ function definitionPublic(definition: CustomToolDefinition) { export class CustomWebMCPRegistry { private definitions = new Map(); private source = ''; + private sourceDirty = false; private revision = 0; private staging: Map | null = null; private pages = new Map(); private sessions = new Map(); + private iframeSessions = new Map(); + private iframeTargetsBySession = new Map(); private activeInvocations = new Map(); private reconciliation: Promise | null = null; private reconcileAgain = false; @@ -194,13 +248,18 @@ export class CustomWebMCPRegistry { private onError?: (message: string) => void; private readonly client: BrowserReplCdpClient; private readonly replID: string; + private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; - constructor(client: BrowserReplCdpClient, replID: string) { + constructor( + client: BrowserReplCdpClient, + replID: string, + runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, + ) { this.client = client; this.replID = replID; + this.runInvocation = runInvocation; this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); - this.scheduleReconcile(); } setErrorHandler(handler: (message: string) => void): void { @@ -219,7 +278,7 @@ export class CustomWebMCPRegistry { destination.set(definition.id, definition); if (!this.staging) { this.revision++; - this.source = ''; + this.sourceDirty = true; this.scheduleReconcile(); } return definitionPublic(definition); @@ -230,7 +289,7 @@ export class CustomWebMCPRegistry { const removed = destination.delete(id); if (removed && !this.staging) { this.revision++; - this.source = ''; + this.sourceDirty = true; this.scheduleReconcile(); } return removed; @@ -254,6 +313,7 @@ export class CustomWebMCPRegistry { await evaluate(`await (async () => {\n${source}\n})()`); this.definitions = this.staging; this.source = source; + this.sourceDirty = false; this.revision++; } finally { this.staging = null; @@ -282,6 +342,7 @@ export class CustomWebMCPRegistry { repl_id: this.replID, revision: this.revision, source: this.source, + source_dirty: this.sourceDirty, tools: this.list(), installations: [...this.pages.values()] .map((page) => ({ @@ -298,7 +359,12 @@ export class CustomWebMCPRegistry { this.unsubscribeEvent(); this.unsubscribeDisconnect(); if (this.reconnectTimer) clearTimeout(this.reconnectTimer); - if (this.reconciliation) await this.reconciliation.catch(() => undefined); + if (this.reconciliation) { + await Promise.race([ + this.reconciliation.catch(() => undefined), + new Promise((resolve) => setTimeout(resolve, 500)), + ]); + } for (const invocation of this.activeInvocations.values()) invocation.controller.abort(); this.activeInvocations.clear(); await Promise.all([...this.pages.values()].map(async (page) => { @@ -306,8 +372,11 @@ export class CustomWebMCPRegistry { if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); await this.detachPage(page); })); + await Promise.all([...this.iframeSessions.values()].map((frame) => this.detachSession(frame.sessionId))); this.pages.clear(); this.sessions.clear(); + this.iframeSessions.clear(); + this.iframeTargetsBySession.clear(); } private validateDefinition(input: CustomToolDefinitionInput, previousRevision: number): CustomToolDefinition { @@ -321,7 +390,7 @@ export class CustomWebMCPRegistry { if (!input.match || !Array.isArray(input.match.url_patterns) || input.match.url_patterns.length === 0) { throw new Error('definition.match.url_patterns must be a non-empty array'); } - for (const pattern of input.match.url_patterns) validatePattern(pattern); + for (const pattern of input.match.url_patterns) parseURLPattern(pattern); if (typeof input.execute !== 'function') throw new Error('definition.execute must be a function'); const toolResult = ToolSchema.safeParse({ ...input.tool, outputSchema: input.outputSchema }); @@ -363,10 +432,16 @@ export class CustomWebMCPRegistry { } if (event.method === 'Target.detachedFromTarget') { const sessionId = (event.params as {sessionId?: string})?.sessionId; - const targetId = sessionId ? this.sessions.get(sessionId) : undefined; - if (sessionId && targetId) { + if (!sessionId) return; + const targetId = this.sessions.get(sessionId); + if (targetId) { this.sessions.delete(sessionId); this.pages.delete(targetId); + } else { + const iframeTargetId = this.iframeTargetsBySession.get(sessionId); + if (!iframeTargetId) return; + this.iframeTargetsBySession.delete(sessionId); + this.iframeSessions.delete(iframeTargetId); } } if ( @@ -385,6 +460,8 @@ export class CustomWebMCPRegistry { private handleDisconnect(): void { this.pages.clear(); this.sessions.clear(); + this.iframeSessions.clear(); + this.iframeTargetsBySession.clear(); if (this.disposed || this.reconnectTimer) return; this.retryReconcile(); } @@ -426,12 +503,20 @@ export class CustomWebMCPRegistry { private async reconcileOnce(): Promise { await this.client.ensureConnected(); - await this.client.browserCommand('Target.setDiscoverTargets', { discover: true }); - const targets = await this.client.listTargets(); + await this.client.send( + 'Target.setDiscoverTargets', + {discover: true}, + undefined, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + const targets = await this.client.listTargets(RECONCILE_COMMAND_TIMEOUT_MS); const pages = targets.filter( (target) => target.type === 'page' && (target.url.startsWith('https://') || target.url.startsWith('http://')), ); const liveTargets = new Set(pages.map((target) => target.targetId)); + const liveIframeTargets = new Set( + targets.filter((target) => target.type === 'iframe').map((target) => target.targetId), + ); for (const [targetId, page] of this.pages) { if (!liveTargets.has(targetId)) { @@ -440,8 +525,15 @@ export class CustomWebMCPRegistry { this.sessions.delete(page.sessionId); } } + for (const [targetId, frame] of this.iframeSessions) { + if (!liveIframeTargets.has(targetId)) { + await this.detachSession(frame.sessionId); + this.iframeSessions.delete(targetId); + this.iframeTargetsBySession.delete(frame.sessionId); + } + } - for (const target of pages) { + await Promise.all(pages.map(async (target) => { try { await this.reconcilePage(target, targets); } catch (error) { @@ -449,16 +541,18 @@ export class CustomWebMCPRegistry { if (page) page.errors = [error instanceof Error ? error.message : String(error)]; else this.reportError(error); } - } + })); } private async reconcilePage(target: CdpTarget, targets: CdpTarget[]): Promise { let page = this.pages.get(target.targetId); if (!page) { - const attached = await this.client.browserCommand<{ sessionId: string }>('Target.attachToTarget', { - targetId: target.targetId, - flatten: true, - }); + const attached = await this.client.send<{sessionId: string}>( + 'Target.attachToTarget', + {targetId: target.targetId, flatten: true}, + undefined, + RECONCILE_COMMAND_TIMEOUT_MS, + ); page = { targetId: target.targetId, sessionId: attached.sessionId, @@ -470,33 +564,50 @@ export class CustomWebMCPRegistry { }; this.pages.set(target.targetId, page); this.sessions.set(page.sessionId, target.targetId); - await this.client.send('Page.enable', undefined, page.sessionId); - await this.client.send('Runtime.enable', undefined, page.sessionId); - } - - const result = await this.client.send<{frameTree: CdpFrameTree}>('Page.getFrameTree', undefined, page.sessionId); - const root = this.decodeFrameTree(result.frameTree); - const frames = flattenFrames(root); - const knownFrameIDs = new Set(frames.map((frame) => frame.id)); - const iframeTargetList = targets.filter((candidate) => candidate.type === 'iframe'); - let added = true; - while (added) { - added = false; - for (const iframe of iframeTargetList) { - if (knownFrameIDs.has(iframe.targetId) || !iframe.parentFrameId || !knownFrameIDs.has(iframe.parentFrameId)) continue; - frames.push({id: iframe.targetId, url: iframe.url}); - knownFrameIDs.add(iframe.targetId); - added = true; - } + await this.client.send('Page.enable', undefined, page.sessionId, RECONCILE_COMMAND_TIMEOUT_MS); + await this.client.send('Runtime.enable', undefined, page.sessionId, RECONCILE_COMMAND_TIMEOUT_MS); } - const iframeTargets = new Set(iframeTargetList.map((candidate) => candidate.targetId)); + + const result = await this.client.send<{frameTree: CdpFrameTree}>( + 'Page.getFrameTree', + undefined, + page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + const root = this.decodeFrameTree(result.frameTree, page.sessionId, target.targetId); + const framesByID = new Map(flattenFrames(root).map((frame) => [frame.id, frame])); + const unresolvedIframes = new Map( + targets + .filter((candidate) => candidate.type === 'iframe') + .map((candidate) => [candidate.targetId, candidate]), + ); + while (unresolvedIframes.size > 0) { + const children = [...unresolvedIframes.values()].filter( + (iframe) => iframe.parentFrameId && framesByID.has(iframe.parentFrameId), + ); + if (children.length === 0) break; + const childTrees = await Promise.all(children.map(async (iframe) => { + unresolvedIframes.delete(iframe.targetId); + const session = await this.ensureIframeSession(iframe.targetId); + const result = await this.client.send<{frameTree: CdpFrameTree}>( + 'Page.getFrameTree', + undefined, + session.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + return flattenFrames(this.decodeFrameTree(result.frameTree, session.sessionId, iframe.targetId)); + })); + for (const frame of childTrees.flat()) framesByID.set(frame.id, frame); + } + const frames = [...framesByID.values()]; const matches = new Map(); for (const definition of this.definitions.values()) { const matchingFrames = frames .filter((frame) => definition.match.url_patterns.some((pattern) => matchesURLPattern(frame.url, pattern))) .map((frame) => ({ frame_id: frame.id, - target_id: iframeTargets.has(frame.id) ? frame.id : frame.id === root.id ? target.targetId : null, + session_id: frame.sessionId, + target_id: frame.targetId, top_target_id: target.targetId, url: normalizedURL(frame.url), })); @@ -508,21 +619,26 @@ export class CustomWebMCPRegistry { const registrationCurrent = page.documentKey === documentKey && page.signature === signature && page.contextId !== null; page.documentKey = documentKey; page.matches = matches; - if (registrationCurrent) return; + if (registrationCurrent || this.disposed) return; - const world = await this.client.send<{ executionContextId: number }>( + await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + if (this.disposed) return; + + const world = await this.client.send<{executionContextId: number}>( 'Page.createIsolatedWorld', - { frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false }, + {frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false}, page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, ); await this.client.send( 'Runtime.addBinding', - { name: BINDING_NAME, executionContextName: WORLD_NAME }, + {name: BINDING_NAME, executionContextName: WORLD_NAME}, page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, ); page.contextId = world.executionContextId; - await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); - await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, world.executionContextId); + if (this.disposed) return; const definitions = [...page.matches.keys()].map((id) => this.definitions.get(id)!); const pageErrors = await this.installPageDefinitions( @@ -540,14 +656,32 @@ export class CustomWebMCPRegistry { page.signature = signature; } - private decodeFrameTree(tree: CdpFrameTree): FrameInfo { + private decodeFrameTree(tree: CdpFrameTree, sessionId: string, targetId: string | null): FrameInfo { return { id: tree.frame.id, + sessionId, + targetId, url: tree.frame.url ?? '', - children: (tree.childFrames ?? []).map((child) => this.decodeFrameTree(child)), + children: (tree.childFrames ?? []).map((child) => this.decodeFrameTree(child, sessionId, null)), }; } + private async ensureIframeSession(targetId: string): Promise { + const current = this.iframeSessions.get(targetId); + if (current) return current; + const attached = await this.client.send<{sessionId: string}>( + 'Target.attachToTarget', + {targetId, flatten: true}, + undefined, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + const session = {targetId, sessionId: attached.sessionId}; + this.iframeSessions.set(targetId, session); + this.iframeTargetsBySession.set(session.sessionId, targetId); + await this.client.send('Page.enable', undefined, session.sessionId, RECONCILE_COMMAND_TIMEOUT_MS); + return session; + } + private async disposePageRuntime(page: PageState, runtimeKey: string, contextId?: number): Promise { const params: Record = { expression: `globalThis[${JSON.stringify(runtimeKey)}]?.dispose?.()`, @@ -567,47 +701,55 @@ export class CustomWebMCPRegistry { runtimeKey: string, contextId?: number, ): Promise { - const serialized = definitions.map((definition) => ({ - id: definition.id, - kind: definition.kind, - tool: definition.tool, - revision: definition.revision, - pageExecuteSource: definition.pageExecuteSource, - })); + const definitionsSource = definitions.map((definition) => { + const metadata = JSON.stringify({ + id: definition.id, + kind: definition.kind, + tool: definition.tool, + revision: definition.revision, + }); + if (definition.kind === 'cdp') return metadata; + return `${metadata.slice(0, -1)},"execute":(${definition.pageExecuteSource})}`; + }).join(','); const expression = ` (async () => { const key = ${JSON.stringify(runtimeKey)}; - globalThis[key]?.dispose?.(); - const definitions = ${JSON.stringify(serialized)}; - const controllers = new Map(); - const pending = new Map(); + const definitions = [${definitionsSource}]; + let runtime = globalThis[key]; + if (!runtime) { + const controllers = new Map(); + const pending = new Map(); + runtime = { + controllers, + pending, + resolveInvocation(id, value) { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.resolve(value); + }, + rejectInvocation(id, message) { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.reject(new Error(message)); + }, + dispose() { + for (const controller of controllers.values()) controller.abort(); + controllers.clear(); + }, + }; + globalThis[key] = runtime; + } + runtime.dispose(); + const {controllers, pending} = runtime; const errors = []; - const resolveInvocation = (id, value) => { - const invocation = pending.get(id); - if (!invocation) return; - pending.delete(id); - invocation.resolve(value); - }; - const rejectInvocation = (id, message) => { - const invocation = pending.get(id); - if (!invocation) return; - pending.delete(id); - invocation.reject(new Error(message)); - }; - globalThis[key] = { - resolveInvocation, - rejectInvocation, - dispose() { - for (const controller of controllers.values()) controller.abort(); - controllers.clear(); - }, - }; for (const definition of definitions) { const controller = new AbortController(); controllers.set(definition.id, controller); let execute; if (definition.kind === 'page') { - execute = (0, eval)(\`(\${definition.pageExecuteSource})\`); + execute = definition.execute; } else { execute = (input, {signal} = {}) => { const invocationId = crypto.randomUUID(); @@ -645,7 +787,12 @@ export class CustomWebMCPRegistry { `; const params: Record = { expression, awaitPromise: true, returnByValue: true }; if (contextId !== undefined) params.contextId = contextId; - const result = await this.client.send('Runtime.evaluate', params, page.sessionId); + const result = await this.client.send( + 'Runtime.evaluate', + params, + page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); if (result.exceptionDetails) { throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? 'tool installation failed'); } @@ -691,10 +838,13 @@ export class CustomWebMCPRegistry { try { const inputResult = definition.inputValidator(payload.input); if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); - const output = await definition.execute(inputResult.data as Record, { - signal: controller.signal, - matches: clone(page.matches.get(definition.id) ?? []), - }); + const output = await this.runInvocation(controller.signal, async () => definition.execute( + inputResult.data as Record, + { + signal: controller.signal, + matches: clone(page.matches.get(definition.id) ?? []), + }, + )); if (definition.outputValidator) { const outputResult = definition.outputValidator(output); if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); @@ -725,6 +875,9 @@ export class CustomWebMCPRegistry { expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.rejectInvocation(${JSON.stringify(invocationId)}, ${JSON.stringify(error)})`; } else { const serialized = JSON.stringify(output ?? null); + if (Buffer.byteLength(serialized) > MAX_OUTPUT_BYTES) { + throw new Error('custom WebMCP output exceeds 1 MiB'); + } expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.resolveInvocation(${JSON.stringify(invocationId)}, ${serialized})`; } try { @@ -735,8 +888,17 @@ export class CustomWebMCPRegistry { } private async detachPage(page: PageState): Promise { + await this.detachSession(page.sessionId); + } + + private async detachSession(sessionId: string): Promise { try { - await this.client.browserCommand('Target.detachFromTarget', { sessionId: page.sessionId }); + await this.client.send( + 'Target.detachFromTarget', + {sessionId}, + undefined, + 1_000, + ); } catch { // The target or connection is already gone. } diff --git a/server/scripts/replhelp/main.mjs b/server/scripts/replhelp/main.mjs index 26800792..7a4911bf 100644 --- a/server/scripts/replhelp/main.mjs +++ b/server/scripts/replhelp/main.mjs @@ -24,6 +24,7 @@ function markdownReference() { repl: 'REPL methods', browser: 'Browser-control methods', webmcp: 'WebMCP methods', + customTools: 'Custom WebMCP methods', }; return Object.keys(titles) .map((group) => { @@ -45,6 +46,7 @@ function openApiMethodList() { ` - REPL: ${names('repl', 'repl.')}`, ` - Browser control: ${names('browser', '')}`, ` - WebMCP: ${names('webmcp', 'webmcp.')}`, + ` - Custom WebMCP: ${names('customTools', 'customTools.')}`, ].join('\n'); } From 1889d4b0d33831cd31088718325f5a114580f697 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:54:59 +0000 Subject: [PATCH 03/18] Reduce custom WebMCP reconciliation churn --- server/docs/repl.md | 2 +- server/runtime/custom-webmcp.ts | 103 ++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/server/docs/repl.md b/server/docs/repl.md index dad32376..449400e4 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -209,7 +209,7 @@ var {ToolSchema} = await import("@modelcontextprotocol/core"); var {AjvJsonSchemaValidator} = await import("@modelcontextprotocol/server/validators/ajv"); ``` -Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A REPL reset or replacement clears the registry and changes `repl_id`. +Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A graceful REPL reset removes registrations, clears the registry, and changes `repl_id`. After an abrupt process death, stale page registrations can remain visible until the next custom-registry GET/PUT starts a fresh REPL and reconciles them, or until their documents navigate; those stale registrations have no live handler. ## Patchright and Playwright Core diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index d4bdd8a7..e51b9d44 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -7,8 +7,10 @@ const BINDING_NAME = '__kernelCustomWebMCPInvoke'; const PAGE_RUNTIME_KEY = '__kernelCustomWebMCP'; const MAIN_PAGE_RUNTIME_KEY = '__kernelCustomWebMCPPage'; const RECONNECT_DELAY_MS = 250; +const RECONCILE_EVENT_DELAY_MS = 50; const RECONCILE_COMMAND_TIMEOUT_MS = 5_000; const MAX_OUTPUT_BYTES = 1 << 20; +const MAX_ERROR_BYTES = 64 << 10; type JsonSchema = Record; type Validator = ReturnType; @@ -131,6 +133,12 @@ function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +function errorMessage(error: unknown): string { + const message = error instanceof Error ? error.message : String(error); + if (Buffer.byteLength(message) <= MAX_ERROR_BYTES) return message; + return `${Buffer.from(message).subarray(0, MAX_ERROR_BYTES).toString('utf8')}...[truncated]`; +} + function formatToolSchemaError(error: { issues: Array<{ path: PropertyKey[]; message: string }> }): string { return error.issues .map((issue) => `${issue.path.length ? issue.path.join('.') : 'tool'}: ${issue.message}`) @@ -233,6 +241,7 @@ export class CustomWebMCPRegistry { private source = ''; private sourceDirty = false; private revision = 0; + private emptyRegistryCleaned = false; private staging: Map | null = null; private pages = new Map(); private sessions = new Map(); @@ -241,6 +250,7 @@ export class CustomWebMCPRegistry { private activeInvocations = new Map(); private reconciliation: Promise | null = null; private reconcileAgain = false; + private reconcileTimer: ReturnType | null = null; private reconnectTimer: ReturnType | null = null; private disposed = false; private readonly unsubscribeEvent: () => void; @@ -290,6 +300,7 @@ export class CustomWebMCPRegistry { if (removed && !this.staging) { this.revision++; this.sourceDirty = true; + if (this.definitions.size === 0) this.emptyRegistryCleaned = false; this.scheduleReconcile(); } return removed; @@ -315,6 +326,7 @@ export class CustomWebMCPRegistry { this.source = source; this.sourceDirty = false; this.revision++; + if (this.definitions.size === 0) this.emptyRegistryCleaned = false; } finally { this.staging = null; } @@ -358,6 +370,7 @@ export class CustomWebMCPRegistry { this.disposed = true; this.unsubscribeEvent(); this.unsubscribeDisconnect(); + if (this.reconcileTimer) clearTimeout(this.reconcileTimer); if (this.reconnectTimer) clearTimeout(this.reconnectTimer); if (this.reconciliation) { await Promise.race([ @@ -467,10 +480,15 @@ export class CustomWebMCPRegistry { } private scheduleReconcile(): void { - queueMicrotask(() => void this.reconcile().catch((error) => { - this.reportError(error); - this.retryReconcile(); - })); + if (this.disposed || this.reconcileTimer) return; + this.reconcileTimer = setTimeout(() => { + this.reconcileTimer = null; + void this.reconcile().catch((error) => { + this.reportError(error); + this.retryReconcile(); + }); + }, RECONCILE_EVENT_DELAY_MS); + this.reconcileTimer.unref?.(); } private retryReconcile(): void { @@ -484,6 +502,10 @@ export class CustomWebMCPRegistry { private async reconcile(): Promise { if (this.disposed) return; + if (this.reconcileTimer) { + clearTimeout(this.reconcileTimer); + this.reconcileTimer = null; + } if (this.reconciliation) { this.reconcileAgain = true; return this.reconciliation; @@ -502,6 +524,7 @@ export class CustomWebMCPRegistry { } private async reconcileOnce(): Promise { + if (this.definitions.size === 0 && this.emptyRegistryCleaned) return; await this.client.ensureConnected(); await this.client.send( 'Target.setDiscoverTargets', @@ -542,6 +565,20 @@ export class CustomWebMCPRegistry { else this.reportError(error); } })); + + if (this.definitions.size === 0) { + await Promise.all([...this.pages.values()].map(async (page) => { + await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + await this.detachPage(page); + })); + await Promise.all([...this.iframeSessions.values()].map((frame) => this.detachSession(frame.sessionId))); + this.pages.clear(); + this.sessions.clear(); + this.iframeSessions.clear(); + this.iframeTargetsBySession.clear(); + this.emptyRegistryCleaned = true; + } } private async reconcilePage(target: CdpTarget, targets: CdpTarget[]): Promise { @@ -616,29 +653,30 @@ export class CustomWebMCPRegistry { const documentKey = `${root.id}:${result.frameTree.frame.loaderId ?? ''}`; const signature = JSON.stringify([...matches.keys()].sort().map((id) => [id, this.definitions.get(id)!.revision])); - const registrationCurrent = page.documentKey === documentKey && page.signature === signature && page.contextId !== null; + const documentChanged = page.documentKey !== documentKey; + const registrationCurrent = !documentChanged && page.signature === signature && page.contextId !== null; page.documentKey = documentKey; page.matches = matches; if (registrationCurrent || this.disposed) return; - await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); - if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); - if (this.disposed) return; - - const world = await this.client.send<{executionContextId: number}>( - 'Page.createIsolatedWorld', - {frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false}, - page.sessionId, - RECONCILE_COMMAND_TIMEOUT_MS, - ); - await this.client.send( - 'Runtime.addBinding', - {name: BINDING_NAME, executionContextName: WORLD_NAME}, - page.sessionId, - RECONCILE_COMMAND_TIMEOUT_MS, - ); - page.contextId = world.executionContextId; + if (documentChanged || page.contextId === null) { + const world = await this.client.send<{executionContextId: number}>( + 'Page.createIsolatedWorld', + {frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false}, + page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + await this.client.send( + 'Runtime.addBinding', + {name: BINDING_NAME, executionContextName: WORLD_NAME}, + page.sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + page.contextId = world.executionContextId; + } if (this.disposed) return; + const isolatedContextId = page.contextId; + if (isolatedContextId === null) throw new Error('custom WebMCP isolated world is unavailable'); const definitions = [...page.matches.keys()].map((id) => this.definitions.get(id)!); const pageErrors = await this.installPageDefinitions( @@ -650,7 +688,7 @@ export class CustomWebMCPRegistry { page, definitions.filter((definition) => definition.kind === 'cdp'), PAGE_RUNTIME_KEY, - world.executionContextId, + isolatedContextId, ); page.errors = [...pageErrors, ...cdpErrors]; page.signature = signature; @@ -719,9 +757,11 @@ export class CustomWebMCPRegistry { if (!runtime) { const controllers = new Map(); const pending = new Map(); + const revisions = new Map(); runtime = { controllers, pending, + revisions, resolveInvocation(id, value) { const invocation = pending.get(id); if (!invocation) return; @@ -737,16 +777,26 @@ export class CustomWebMCPRegistry { dispose() { for (const controller of controllers.values()) controller.abort(); controllers.clear(); + revisions.clear(); }, }; globalThis[key] = runtime; } - runtime.dispose(); - const {controllers, pending} = runtime; + const {controllers, pending, revisions} = runtime; + const desiredRevisions = new Map(definitions.map(definition => [definition.id, definition.revision])); + for (const [id, controller] of controllers) { + if (desiredRevisions.get(id) !== revisions.get(id)) { + controller.abort(); + controllers.delete(id); + revisions.delete(id); + } + } const errors = []; for (const definition of definitions) { + if (controllers.has(definition.id)) continue; const controller = new AbortController(); controllers.set(definition.id, controller); + revisions.set(definition.id, definition.revision); let execute; if (definition.kind === 'page') { execute = definition.execute; @@ -779,6 +829,7 @@ export class CustomWebMCPRegistry { } catch (error) { controller.abort(); controllers.delete(definition.id); + revisions.delete(definition.id); errors.push(\`\${definition.id}: \${error instanceof Error ? error.message : String(error)}\`); } } @@ -856,7 +907,7 @@ export class CustomWebMCPRegistry { params.executionContextId, payload.invocation_id, undefined, - error instanceof Error ? error.message : String(error), + errorMessage(error), ); } finally { this.activeInvocations.delete(payload.invocation_id); From c5e2a0879d6cef5c3f36b1f07b20f3555214c297 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:07:52 +0000 Subject: [PATCH 04/18] Harden custom WebMCP cleanup recovery --- server/docs/repl.md | 2 +- server/runtime/custom-webmcp.ts | 71 +++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/server/docs/repl.md b/server/docs/repl.md index 449400e4..c3c1ea5b 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -209,7 +209,7 @@ var {ToolSchema} = await import("@modelcontextprotocol/core"); var {AjvJsonSchemaValidator} = await import("@modelcontextprotocol/server/validators/ajv"); ``` -Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A graceful REPL reset removes registrations, clears the registry, and changes `repl_id`. After an abrupt process death, stale page registrations can remain visible until the next custom-registry GET/PUT starts a fresh REPL and reconciles them, or until their documents navigate; those stale registrations have no live handler. +Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A graceful REPL reset removes registrations, clears the registry, and changes `repl_id`. After an abrupt process death, stale page registrations can remain visible until the next Browser REPL starts and performs its one-time empty-registry cleanup, or until their documents navigate; those stale registrations have no live handler and invocations time out. ## Patchright and Playwright Core diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index e51b9d44..97776291 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -252,6 +252,7 @@ export class CustomWebMCPRegistry { private reconcileAgain = false; private reconcileTimer: ReturnType | null = null; private reconnectTimer: ReturnType | null = null; + private connectionGeneration = 0; private disposed = false; private readonly unsubscribeEvent: () => void; private readonly unsubscribeDisconnect: () => void; @@ -270,6 +271,7 @@ export class CustomWebMCPRegistry { this.runInvocation = runInvocation; this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); + this.scheduleReconcile(); } setErrorHandler(handler: (message: string) => void): void { @@ -471,6 +473,8 @@ export class CustomWebMCPRegistry { } private handleDisconnect(): void { + this.connectionGeneration++; + this.emptyRegistryCleaned = false; this.pages.clear(); this.sessions.clear(); this.iframeSessions.clear(); @@ -561,23 +565,38 @@ export class CustomWebMCPRegistry { await this.reconcilePage(target, targets); } catch (error) { const page = this.pages.get(target.targetId); - if (page) page.errors = [error instanceof Error ? error.message : String(error)]; - else this.reportError(error); + if (page) { + const message = errorMessage(error); + page.errors = [message]; + if (message.includes('Cannot find context') || message.includes('Session with given id not found')) { + page.contextId = null; + page.signature = ''; + } + } else { + this.reportError(error); + } } })); if (this.definitions.size === 0) { - await Promise.all([...this.pages.values()].map(async (page) => { - await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); - if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); - await this.detachPage(page); + const cleanupGeneration = this.connectionGeneration; + const pageCleanup = await Promise.all([...this.pages.values()].map(async (page) => { + const mainDisposed = await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + const isolatedDisposed = page.contextId === null || + await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + const detached = await this.detachPage(page); + return mainDisposed && isolatedDisposed && detached; })); - await Promise.all([...this.iframeSessions.values()].map((frame) => this.detachSession(frame.sessionId))); + const frameCleanup = await Promise.all( + [...this.iframeSessions.values()].map((frame) => this.detachSession(frame.sessionId)), + ); this.pages.clear(); this.sessions.clear(); this.iframeSessions.clear(); this.iframeTargetsBySession.clear(); - this.emptyRegistryCleaned = true; + this.emptyRegistryCleaned = cleanupGeneration === this.connectionGeneration && + pageCleanup.every(Boolean) && frameCleanup.every(Boolean); + if (!this.emptyRegistryCleaned) this.retryReconcile(); } } @@ -590,6 +609,10 @@ export class CustomWebMCPRegistry { undefined, RECONCILE_COMMAND_TIMEOUT_MS, ); + if (this.disposed) { + await this.detachSession(attached.sessionId); + return; + } page = { targetId: target.targetId, sessionId: attached.sessionId, @@ -603,6 +626,12 @@ export class CustomWebMCPRegistry { this.sessions.set(page.sessionId, target.targetId); await this.client.send('Page.enable', undefined, page.sessionId, RECONCILE_COMMAND_TIMEOUT_MS); await this.client.send('Runtime.enable', undefined, page.sessionId, RECONCILE_COMMAND_TIMEOUT_MS); + if (this.disposed) { + this.pages.delete(target.targetId); + this.sessions.delete(page.sessionId); + await this.detachPage(page); + return; + } } const result = await this.client.send<{frameTree: CdpFrameTree}>( @@ -655,11 +684,11 @@ export class CustomWebMCPRegistry { const signature = JSON.stringify([...matches.keys()].sort().map((id) => [id, this.definitions.get(id)!.revision])); const documentChanged = page.documentKey !== documentKey; const registrationCurrent = !documentChanged && page.signature === signature && page.contextId !== null; - page.documentKey = documentKey; page.matches = matches; if (registrationCurrent || this.disposed) return; if (documentChanged || page.contextId === null) { + page.contextId = null; const world = await this.client.send<{executionContextId: number}>( 'Page.createIsolatedWorld', {frameId: root.id, worldName: WORLD_NAME, grantUniveralAccess: false}, @@ -691,6 +720,7 @@ export class CustomWebMCPRegistry { isolatedContextId, ); page.errors = [...pageErrors, ...cdpErrors]; + page.documentKey = documentKey; page.signature = signature; } @@ -720,7 +750,7 @@ export class CustomWebMCPRegistry { return session; } - private async disposePageRuntime(page: PageState, runtimeKey: string, contextId?: number): Promise { + private async disposePageRuntime(page: PageState, runtimeKey: string, contextId?: number): Promise { const params: Record = { expression: `globalThis[${JSON.stringify(runtimeKey)}]?.dispose?.()`, awaitPromise: true, @@ -728,8 +758,12 @@ export class CustomWebMCPRegistry { if (contextId !== undefined) params.contextId = contextId; try { await this.client.send('Runtime.evaluate', params, page.sessionId, 1_000); - } catch { - // A navigation may already have destroyed this runtime. + return true; + } catch (error) { + const message = errorMessage(error); + return message.includes('Cannot find context') || + message.includes('No target with given id') || + message.includes('Session with given id not found'); } } @@ -938,11 +972,11 @@ export class CustomWebMCPRegistry { } } - private async detachPage(page: PageState): Promise { - await this.detachSession(page.sessionId); + private async detachPage(page: PageState): Promise { + return this.detachSession(page.sessionId); } - private async detachSession(sessionId: string): Promise { + private async detachSession(sessionId: string): Promise { try { await this.client.send( 'Target.detachFromTarget', @@ -950,8 +984,11 @@ export class CustomWebMCPRegistry { undefined, 1_000, ); - } catch { - // The target or connection is already gone. + return true; + } catch (error) { + const message = errorMessage(error); + return message.includes('No target with given id') || + message.includes('Session with given id not found'); } } From ec92c21af91ead8ee3632bf69f857c249aad6c5e Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:12:34 +0000 Subject: [PATCH 05/18] Recover custom WebMCP worlds after install errors --- server/runtime/custom-webmcp.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index 97776291..2cef7565 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -95,6 +95,7 @@ interface PageState { targetId: string; sessionId: string; documentKey: string; + contextDocumentKey: string; contextId: number | null; signature: string; matches: Map; @@ -570,6 +571,7 @@ export class CustomWebMCPRegistry { page.errors = [message]; if (message.includes('Cannot find context') || message.includes('Session with given id not found')) { page.contextId = null; + page.contextDocumentKey = ''; page.signature = ''; } } else { @@ -617,6 +619,7 @@ export class CustomWebMCPRegistry { targetId: target.targetId, sessionId: attached.sessionId, documentKey: '', + contextDocumentKey: '', contextId: null, signature: '', matches: new Map(), @@ -683,11 +686,12 @@ export class CustomWebMCPRegistry { const documentKey = `${root.id}:${result.frameTree.frame.loaderId ?? ''}`; const signature = JSON.stringify([...matches.keys()].sort().map((id) => [id, this.definitions.get(id)!.revision])); const documentChanged = page.documentKey !== documentKey; + const contextChanged = page.contextDocumentKey !== documentKey; const registrationCurrent = !documentChanged && page.signature === signature && page.contextId !== null; page.matches = matches; if (registrationCurrent || this.disposed) return; - if (documentChanged || page.contextId === null) { + if (contextChanged || page.contextId === null) { page.contextId = null; const world = await this.client.send<{executionContextId: number}>( 'Page.createIsolatedWorld', @@ -702,6 +706,7 @@ export class CustomWebMCPRegistry { RECONCILE_COMMAND_TIMEOUT_MS, ); page.contextId = world.executionContextId; + page.contextDocumentKey = documentKey; } if (this.disposed) return; const isolatedContextId = page.contextId; From c327ff784752cfa001c8f5962a9085442c0a1a3e Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:20:35 +0000 Subject: [PATCH 06/18] Refine custom WebMCP tool registration --- server/cmd/api/api/browser_repl.go | 80 +- server/cmd/api/api/browser_repl_cells_test.go | 6 +- server/cmd/api/api/custom_webmcp.go | 171 +- server/cmd/api/api/custom_webmcp_test.go | 152 +- server/cmd/api/api/middleware.go | 8 +- server/cmd/api/api/middleware_test.go | 2 +- server/cmd/api/api/webmcp.go | 50 +- server/cmd/api/api/webmcp_test.go | 41 +- server/docs/repl.md | 66 +- server/e2e/e2e_webmcp_test.go | 19 +- server/lib/events/category_gen.go | 5 +- server/lib/oapi/oapi.go | 1924 ++++++++++------- server/lib/webmcpclient/client.go | 45 +- .../lib/webmcpclient/custom_tool_name_test.go | 17 + server/lib/webmcpclient/types.go | 19 +- server/openapi.yaml | 199 +- server/runtime/browser-repl-help.ts | 40 +- server/runtime/browser-repl.ts | 59 +- server/runtime/custom-webmcp.ts | 214 +- server/runtime/webmcp.test.ts | 10 +- server/runtime/webmcp.ts | 29 +- server/scripts/replhelp/main.mjs | 2 - 22 files changed, 1854 insertions(+), 1304 deletions(-) create mode 100644 server/lib/webmcpclient/custom_tool_name_test.go diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 0f4f2137..2b379a44 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -14,6 +14,7 @@ import ( "os" "os/exec" "strings" + "sync" "time" "github.com/google/uuid" @@ -74,10 +75,12 @@ type browserReplChild struct { // browserReplManager owns execution admission and the persistent Node child. // Lifecycle synchronization stays behind its Execute and Shutdown methods. type browserReplManager struct { - admission chan struct{} - lifecycle context.Context - stop context.CancelCauseFunc - child *browserReplChild // guarded by admission + admission chan struct{} + lifecycle context.Context + stop context.CancelCauseFunc + child *browserReplChild // guarded by admission + customToolsMu sync.RWMutex + customTools map[string]oapi.CustomWebMCPDefinition } func newBrowserReplManager() *browserReplManager { @@ -85,12 +88,32 @@ func newBrowserReplManager() *browserReplManager { admission := make(chan struct{}, 1) admission <- struct{}{} return &browserReplManager{ - admission: admission, - lifecycle: lifecycle, - stop: stop, + admission: admission, + lifecycle: lifecycle, + stop: stop, + customTools: make(map[string]oapi.CustomWebMCPDefinition), } } +func (m *browserReplManager) setCustomTools(tools []oapi.CustomWebMCPDefinition) { + m.customToolsMu.Lock() + defer m.customToolsMu.Unlock() + m.customTools = make(map[string]oapi.CustomWebMCPDefinition, len(tools)) + for _, tool := range tools { + m.customTools[tool.Id] = tool + } +} + +func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPDefinition { + m.customToolsMu.RLock() + defer m.customToolsMu.RUnlock() + tools := make(map[string]oapi.CustomWebMCPDefinition, len(m.customTools)) + for id, tool := range m.customTools { + tools[id] = tool + } + return tools +} + // browserReplSocketPath returns the Unix socket path for the REPL daemon. // Overridable for tests. func browserReplSocketPath() string { @@ -206,6 +229,7 @@ func closedWaitChannel(err error) chan error { func (m *browserReplManager) clearLocked(ctx context.Context, child *browserReplChild) { if m.child == child { m.child = nil + m.setCustomTools(nil) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) } @@ -348,10 +372,12 @@ func (m *browserReplManager) killLocked(ctx context.Context, reason string) { // browserReplDaemonRequest is the wire format sent to the REPL daemon. type browserReplDaemonRequest struct { - ID string `json:"id"` - Code string `json:"code"` - Operation string `json:"operation,omitempty"` - TimeoutMs int `json:"timeout_ms,omitempty"` + ID string `json:"id"` + Code string `json:"code"` + Operation string `json:"operation,omitempty"` + Namespace string `json:"namespace,omitempty"` + CustomToolID string `json:"custom_tool_id,omitempty"` + TimeoutMs int `json:"timeout_ms,omitempty"` } // browserReplDaemonResponse is the wire format returned by the REPL daemon. @@ -360,6 +386,7 @@ type browserReplDaemonResponse struct { ReplID string `json:"repl_id"` Success bool `json:"success"` Error string `json:"error,omitempty"` + ErrorCode string `json:"error_code,omitempty"` Stack *string `json:"stack,omitempty"` Content []json.RawMessage `json:"content,omitempty"` ContentTruncated bool `json:"content_truncated"` @@ -372,9 +399,10 @@ type browserReplDaemonResponse struct { // exception details and is exiting non-zero. The API treats it like a // timeout — terminate the handle and report repl_terminated — so the // state loss is explicit to the caller. - Exiting bool `json:"exiting,omitempty"` - Result json.RawMessage `json:"result,omitempty"` - DurationMs int `json:"duration_ms"` + Exiting bool `json:"exiting,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + CustomTools *[]oapi.CustomWebMCPDefinition `json:"custom_tools,omitempty"` + DurationMs int `json:"duration_ms"` } // browserReplRequest is the already-encoded request sent over the daemon @@ -393,15 +421,24 @@ func prepareBrowserReplRequest(code string, timeout time.Duration) (*browserRepl } func prepareBrowserReplOperation(code, operation string, timeout time.Duration) (*browserReplRequest, error) { + return prepareBrowserReplOperationWithCustomTools(code, operation, "", "", timeout) +} + +func prepareBrowserReplOperationWithCustomTools( + code, operation, namespace, customToolID string, + timeout time.Duration, +) (*browserReplRequest, error) { id := uuid.New().String() var buf bytes.Buffer encoder := json.NewEncoder(&buf) encoder.SetEscapeHTML(false) if err := encoder.Encode(browserReplDaemonRequest{ - ID: id, - Code: code, - Operation: operation, - TimeoutMs: int(timeout.Milliseconds()), + ID: id, + Code: code, + Operation: operation, + Namespace: namespace, + CustomToolID: customToolID, + TimeoutMs: int(timeout.Milliseconds()), }); err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } @@ -497,6 +534,9 @@ func (m *browserReplManager) executeLocked(ctx context.Context, request *browser if resp.ReplID != child.id { return nil, fmt.Errorf("response repl_id mismatch: expected %s, got %s", child.id, resp.ReplID) } + if resp.CustomTools != nil { + m.setCustomTools(*resp.CustomTools) + } return &resp, nil } @@ -553,8 +593,8 @@ func StrictBrowserReplBodyMiddleware(next http.Handler) http.Handler { case r.Method == http.MethodPost && r.URL.Path == "/repl": probe = &oapi.BrowserReplRequest{} maxBytes = maxBrowserReplBodyBytes - case r.Method == http.MethodPut && r.URL.Path == "/webmcp/custom-tools": - probe = &oapi.CustomWebMCPRegistryRequest{} + case r.Method == http.MethodPost && r.URL.Path == "/webmcp/custom-tools": + probe = &oapi.AddCustomWebMCPToolsRequest{} maxBytes = maxCustomWebMCPRequestBytes default: next.ServeHTTP(w, r) diff --git a/server/cmd/api/api/browser_repl_cells_test.go b/server/cmd/api/api/browser_repl_cells_test.go index 4b48887e..5a164c9b 100644 --- a/server/cmd/api/api/browser_repl_cells_test.go +++ b/server/cmd/api/api/browser_repl_cells_test.go @@ -410,14 +410,14 @@ func TestStrictBrowserReplBodyMiddleware(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = httptest.NewRecorder() - handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", strings.NewReader(`{"source":"","bogus":1}`))) + handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/webmcp/custom-tools", strings.NewReader(`{"namespace":"example.com","source":"","bogus":1}`))) require.Equal(t, http.StatusBadRequest, rec.Code) require.Contains(t, rec.Body.String(), `unknown field \"bogus\"`) rec = httptest.NewRecorder() - handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", strings.NewReader(`{"source":"customTools.register({})"}`))) + handler.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/webmcp/custom-tools", strings.NewReader(`{"namespace":"example.com","source":"[]"}`))) require.Equal(t, http.StatusOK, rec.Code) - require.JSONEq(t, `{"source":"customTools.register({})"}`, rec.Body.String()) + require.JSONEq(t, `{"namespace":"example.com","source":"[]"}`, rec.Body.String()) rec = httptest.NewRecorder() handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/repl", nil)) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index c5374183..dce6d06b 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -15,67 +15,107 @@ const ( customWebMCPOperationTimeout = 60 * time.Second maxCustomWebMCPSourceBytes = 8_000_000 maxCustomWebMCPRequestBytes = maxCustomWebMCPSourceBytes + (4 << 10) - customWebMCPGetOperation = "custom_tools_get" - customWebMCPReplaceOperation = "custom_tools_replace" + customWebMCPListOperation = "custom_tools_list" + customWebMCPAddOperation = "custom_tools_add" + customWebMCPRemoveOperation = "custom_tool_remove" ) -type customWebMCPRuntimeSnapshot struct { - ReplID string `json:"repl_id"` - Revision int `json:"revision"` - Source string `json:"source"` - SourceDirty bool `json:"source_dirty"` - Tools []customWebMCPRuntimeDefinition `json:"tools"` - Installations []oapi.CustomWebMCPInstallation `json:"installations"` +type customWebMCPExecutionError struct { + code string + message string } -type customWebMCPRuntimeDefinition struct { - ID string `json:"id"` - Kind string `json:"kind"` - Match oapi.CustomWebMCPMatch `json:"match"` - Tool map[string]any `json:"tool"` - OutputSchema *map[string]any `json:"outputSchema,omitempty"` - Revision int `json:"revision"` -} +func (e *customWebMCPExecutionError) Error() string { return e.message } -func (s *ApiService) GetCustomWebMCPTools(ctx context.Context, _ oapi.GetCustomWebMCPToolsRequestObject) (oapi.GetCustomWebMCPToolsResponseObject, error) { - snapshot, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPGetOperation, "") +func (s *ApiService) ListCustomWebMCPTools(ctx context.Context, _ oapi.ListCustomWebMCPToolsRequestObject) (oapi.ListCustomWebMCPToolsResponseObject, error) { + tools, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPListOperation, "", "", "") if err != nil { - logger.FromContext(ctx).Error("failed to get custom WebMCP tools", "err", err) - return oapi.GetCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to get custom WebMCP tools"}}, nil + logger.FromContext(ctx).Error("failed to list custom WebMCP tools", "err", err) + return oapi.ListCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to list custom WebMCP tools"}}, nil } - return oapi.GetCustomWebMCPTools200JSONResponse(snapshot), nil + return oapi.ListCustomWebMCPTools200JSONResponse{Tools: tools}, nil } -func (s *ApiService) ReplaceCustomWebMCPTools(ctx context.Context, request oapi.ReplaceCustomWebMCPToolsRequestObject) (oapi.ReplaceCustomWebMCPToolsResponseObject, error) { +func (s *ApiService) AddCustomWebMCPTools(ctx context.Context, request oapi.AddCustomWebMCPToolsRequestObject) (oapi.AddCustomWebMCPToolsResponseObject, error) { if request.Body == nil { - return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil + return oapi.AddCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil } if len(request.Body.Source) > maxCustomWebMCPSourceBytes { - return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source must not exceed 8000000 bytes"}}, nil + return oapi.AddCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "source must not exceed 8000000 bytes"}}, nil } RecordTelemetryCode(ctx, request.Body.Source) - snapshot, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPReplaceOperation, request.Body.Source) + tools, err := s.browserRepl.customWebMCPOperation( + ctx, + customWebMCPAddOperation, + request.Body.Source, + request.Body.Namespace, + "", + ) if err != nil { var executionErr *customWebMCPExecutionError if errors.As(err, &executionErr) { - return oapi.ReplaceCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: executionErr.Error()}}, nil + if executionErr.code == "custom_tool_conflict" { + return oapi.AddCustomWebMCPTools409JSONResponse{ConflictErrorJSONResponse: oapi.ConflictErrorJSONResponse{Message: executionErr.Error()}}, nil + } + return oapi.AddCustomWebMCPTools400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: executionErr.Error()}}, nil } - logger.FromContext(ctx).Error("failed to replace custom WebMCP tools", "err", err) - return oapi.ReplaceCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to replace custom WebMCP tools"}}, nil + logger.FromContext(ctx).Error("failed to add custom WebMCP tools", "err", err) + return oapi.AddCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to add custom WebMCP tools"}}, nil } - return oapi.ReplaceCustomWebMCPTools200JSONResponse(snapshot), nil + return oapi.AddCustomWebMCPTools201JSONResponse{Tools: tools}, nil } -type customWebMCPExecutionError struct { - message string +func (s *ApiService) RemoveCustomWebMCPTool(ctx context.Context, request oapi.RemoveCustomWebMCPToolRequestObject) (oapi.RemoveCustomWebMCPToolResponseObject, error) { + removed, err := s.browserRepl.removeCustomWebMCPTool(ctx, request.Id) + if err != nil { + logger.FromContext(ctx).Error("failed to remove custom WebMCP tool", "err", err) + return oapi.RemoveCustomWebMCPTool500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to remove custom WebMCP tool"}}, nil + } + if !removed { + return oapi.RemoveCustomWebMCPTool404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "custom WebMCP tool not found"}}, nil + } + return oapi.RemoveCustomWebMCPTool204Response{}, nil } -func (e *customWebMCPExecutionError) Error() string { return e.message } +func (m *browserReplManager) removeCustomWebMCPTool(ctx context.Context, id string) (bool, error) { + result, err := m.customWebMCPOperationRaw(ctx, customWebMCPRemoveOperation, "", "", id) + if err != nil { + return false, err + } + var removed bool + if err := json.Unmarshal(result, &removed); err != nil { + return false, fmt.Errorf("decode custom WebMCP removal: %w", err) + } + return removed, nil +} -func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operation, source string) (oapi.CustomWebMCPRegistry, error) { +func (m *browserReplManager) customWebMCPOperation( + ctx context.Context, + operation, source, namespace, customToolID string, +) ([]oapi.CustomWebMCPDefinition, error) { + result, err := m.customWebMCPOperationRaw(ctx, operation, source, namespace, customToolID) + if err != nil { + return nil, err + } + tools := make([]oapi.CustomWebMCPDefinition, 0) + if err := json.Unmarshal(result, &tools); err != nil { + return nil, fmt.Errorf("decode custom WebMCP tools: %w", err) + } + for _, tool := range tools { + if tool.Kind != "page" && tool.Kind != "cdp" { + return nil, fmt.Errorf("custom WebMCP tool %s has invalid kind %q", tool.Id, tool.Kind) + } + } + return tools, nil +} + +func (m *browserReplManager) customWebMCPOperationRaw( + ctx context.Context, + operation, source, namespace, customToolID string, +) (json.RawMessage, error) { if err := m.acquire(ctx); err != nil { - return oapi.CustomWebMCPRegistry{}, err + return nil, err } defer m.release() @@ -85,23 +125,29 @@ func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operatio cancelOperation(nil) }() if err := context.Cause(operationCtx); err != nil { - return oapi.CustomWebMCPRegistry{}, err + return nil, err } ctx = operationCtx - request, err := prepareBrowserReplOperation(source, operation, customWebMCPOperationTimeout) + request, err := prepareBrowserReplOperationWithCustomTools( + source, + operation, + namespace, + customToolID, + customWebMCPOperationTimeout, + ) if err != nil { - return oapi.CustomWebMCPRegistry{}, &customWebMCPExecutionError{message: err.Error()} + return nil, &customWebMCPExecutionError{message: err.Error()} } if err := m.ensureLocked(ctx); err != nil { - return oapi.CustomWebMCPRegistry{}, fmt.Errorf("start Browser REPL: %w", err) + return nil, fmt.Errorf("start Browser REPL: %w", err) } replID := m.child.id response, err := m.executeLocked(ctx, request, customWebMCPOperationTimeout) if err != nil { var notDispatched *browserReplNotDispatchedError if errors.As(err, ¬Dispatched) { - return oapi.CustomWebMCPRegistry{}, notDispatched.cause + return nil, notDispatched.cause } var timeoutErr *browserReplTimeoutError if errors.As(err, &timeoutErr) { @@ -109,52 +155,17 @@ func (m *browserReplManager) customWebMCPOperation(ctx context.Context, operatio } else { m.terminateLocked(ctx, "custom WebMCP operation failure") } - return oapi.CustomWebMCPRegistry{}, err + return nil, err } if response.TimedOut || response.Exiting { m.terminateLocked(ctx, "custom WebMCP operation terminated Browser REPL") - return oapi.CustomWebMCPRegistry{}, fmt.Errorf("Browser REPL %s terminated: %s", replID, response.Error) + return nil, fmt.Errorf("Browser REPL %s terminated: %s", replID, response.Error) } if !response.Success { - return oapi.CustomWebMCPRegistry{}, &customWebMCPExecutionError{message: response.Error} + return nil, &customWebMCPExecutionError{code: response.ErrorCode, message: response.Error} } if len(response.Result) == 0 { - return oapi.CustomWebMCPRegistry{}, errors.New("Browser REPL returned no custom WebMCP registry") - } - - var runtime customWebMCPRuntimeSnapshot - if err := json.Unmarshal(response.Result, &runtime); err != nil { - m.terminateLocked(ctx, "invalid custom WebMCP response") - return oapi.CustomWebMCPRegistry{}, fmt.Errorf("decode custom WebMCP registry: %w", err) - } - if runtime.ReplID != replID { - m.terminateLocked(ctx, "custom WebMCP repl_id mismatch") - return oapi.CustomWebMCPRegistry{}, fmt.Errorf("custom WebMCP repl_id mismatch: expected %s, got %s", replID, runtime.ReplID) + return nil, errors.New("Browser REPL returned no custom WebMCP result") } - - tools := make([]oapi.CustomWebMCPDefinition, 0, len(runtime.Tools)) - for _, tool := range runtime.Tools { - if tool.Kind != "page" && tool.Kind != "cdp" { - return oapi.CustomWebMCPRegistry{}, fmt.Errorf("custom WebMCP tool %s has invalid kind %q", tool.ID, tool.Kind) - } - tools = append(tools, oapi.CustomWebMCPDefinition{ - Id: tool.ID, - Kind: tool.Kind, - Match: tool.Match, - Tool: tool.Tool, - OutputSchema: tool.OutputSchema, - Revision: tool.Revision, - }) - } - if runtime.Installations == nil { - runtime.Installations = make([]oapi.CustomWebMCPInstallation, 0) - } - return oapi.CustomWebMCPRegistry{ - ReplId: runtime.ReplID, - Revision: runtime.Revision, - Source: runtime.Source, - SourceDirty: runtime.SourceDirty, - Tools: tools, - Installations: runtime.Installations, - }, nil + return response.Result, nil } diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index f99cd047..57c8e2a7 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -8,99 +8,131 @@ import ( "github.com/stretchr/testify/require" ) -const customWebMCPTestSource = ` -customTools.register({ - id: "test/read-title", - kind: "cdp", - match: {url_patterns: ["https://example.com/*"]}, - tool: { - name: "read_title", - description: "Read the current page title.", - inputSchema: {type: "object", additionalProperties: false}, - annotations: {readOnlyHint: true}, +const customWebMCPTestSource = `[ + { + kind: "cdp", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_title", + description: "Read the current page title.", + inputSchema: {type: "object", additionalProperties: false}, + outputSchema: { + type: "object", + properties: {title: {type: "string"}}, + required: ["title"], + additionalProperties: false, + }, + annotations: {readOnlyHint: true}, + }, + execute: async () => ({title: await js(() => document.title)}), }, - outputSchema: { - type: "object", - properties: {title: {type: "string"}}, - required: ["title"], - additionalProperties: false, - }, - execute: async () => ({title: await js(() => document.title)}), -}); -` +]` -func TestCustomWebMCPRegistryUsesBrowserReplLifecycle(t *testing.T) { +func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() - replaced, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ - Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + added, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "example.com", + Source: customWebMCPTestSource, + }, }) require.NoError(t, err) - body := replaced.(oapi.ReplaceCustomWebMCPTools200JSONResponse) - require.NotEmpty(t, body.ReplId) - require.Equal(t, 1, body.Revision) - require.Equal(t, customWebMCPTestSource, body.Source) - require.False(t, body.SourceDirty) + body := added.(oapi.AddCustomWebMCPTools201JSONResponse) require.Len(t, body.Tools, 1) - require.Equal(t, "test/read-title", body.Tools[0].Id) + require.Regexp(t, `^ct_[a-f0-9]{16}$`, body.Tools[0].Id) + require.Equal(t, "example.com", body.Tools[0].Namespace) require.Equal(t, "cdp", body.Tools[0].Kind) + require.Equal(t, "read_title", body.Tools[0].Tool.Name) require.Equal(t, []string{"https://example.com/*"}, body.Tools[0].Match.UrlPatterns) - require.Empty(t, body.Installations) + require.Equal(t, body.Tools[0], svc.browserRepl.customToolsSnapshot()[body.Tools[0].Id]) - got, err := svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + listed, err := svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) require.NoError(t, err) - current := got.(oapi.GetCustomWebMCPTools200JSONResponse) - require.Equal(t, body.ReplId, current.ReplId) - require.Equal(t, body.Revision, current.Revision) + current := listed.(oapi.ListCustomWebMCPTools200JSONResponse) require.Equal(t, body.Tools, current.Tools) - replaced, err = svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ - Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + conflict, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "example.com", + Source: customWebMCPTestSource, + }, }) require.NoError(t, err) - second := replaced.(oapi.ReplaceCustomWebMCPTools200JSONResponse) - require.Equal(t, 2, second.Revision) - require.Equal(t, 2, second.Tools[0].Revision) + _, ok := conflict.(oapi.AddCustomWebMCPTools409JSONResponse) + require.True(t, ok, "expected 409 response, got %T", conflict) + + removed, err := svc.RemoveCustomWebMCPTool(ctx, oapi.RemoveCustomWebMCPToolRequestObject{Id: body.Tools[0].Id}) + require.NoError(t, err) + _, ok = removed.(oapi.RemoveCustomWebMCPTool204Response) + require.True(t, ok, "expected 204 response, got %T", removed) - requireExec(t, svc, `customTools.remove("test/read-title")`, nil) - got, err = svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + listed, err = svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) require.NoError(t, err) - dirty := got.(oapi.GetCustomWebMCPTools200JSONResponse) - require.True(t, dirty.SourceDirty) - require.Equal(t, customWebMCPTestSource, dirty.Source) - require.Empty(t, dirty.Tools) + require.Empty(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools) + _, err = svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "example.com", + Source: customWebMCPTestSource, + }, + }) + require.NoError(t, err) reset := true - resetResponse := executeBrowserRepl(t, svc, &oapi.ExecuteBrowserReplJSONRequestBody{Code: "", Reset: &reset}) - require.NotEqual(t, body.ReplId, resetResponse.ReplId) - got, err = svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + executeBrowserRepl(t, svc, &oapi.ExecuteBrowserReplJSONRequestBody{Code: "", Reset: &reset}) + listed, err = svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + require.Empty(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools) +} + +func TestCustomWebMCPAllowsSameNameAcrossNamespaces(t *testing.T) { + svc := newBrowserReplSvc(t) + ctx := context.Background() + + for _, namespace := range []string{"one.example", "two.example"} { + response, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: namespace, + Source: customWebMCPTestSource, + }, + }) + require.NoError(t, err) + added, ok := response.(oapi.AddCustomWebMCPTools201JSONResponse) + require.True(t, ok, "expected 201 response, got %T", response) + require.Equal(t, namespace, added.Tools[0].Namespace) + } + + listed, err := svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) require.NoError(t, err) - fresh := got.(oapi.GetCustomWebMCPTools200JSONResponse) - require.Equal(t, resetResponse.ReplId, fresh.ReplId) - require.Zero(t, fresh.Revision) - require.Empty(t, fresh.Tools) + require.Len(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools, 2) } -func TestCustomWebMCPInvalidReplacementPreservesRegistry(t *testing.T) { +func TestCustomWebMCPInvalidAdditionPreservesTools(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() - _, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ - Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: customWebMCPTestSource}, + _, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "example.com", + Source: customWebMCPTestSource, + }, }) require.NoError(t, err) - response, err := svc.ReplaceCustomWebMCPTools(ctx, oapi.ReplaceCustomWebMCPToolsRequestObject{ - Body: &oapi.ReplaceCustomWebMCPToolsJSONRequestBody{Source: `customTools.register({id: "broken"})`}, + response, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "broken.example", + Source: `[{kind: "cdp"}]`, + }, }) require.NoError(t, err) - _, ok := response.(oapi.ReplaceCustomWebMCPTools400JSONResponse) + _, ok := response.(oapi.AddCustomWebMCPTools400JSONResponse) require.True(t, ok, "expected 400 response, got %T", response) - got, err := svc.GetCustomWebMCPTools(ctx, oapi.GetCustomWebMCPToolsRequestObject{}) + listed, err := svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) require.NoError(t, err) - current := got.(oapi.GetCustomWebMCPTools200JSONResponse) + current := listed.(oapi.ListCustomWebMCPTools200JSONResponse) require.Len(t, current.Tools, 1) - require.Equal(t, "test/read-title", current.Tools[0].Id) + require.Equal(t, "read_title", current.Tools[0].Tool.Name) } diff --git a/server/cmd/api/api/middleware.go b/server/cmd/api/api/middleware.go index de31fe13..7d347087 100644 --- a/server/cmd/api/api/middleware.go +++ b/server/cmd/api/api/middleware.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net/http" + "strings" "sync/atomic" "time" @@ -128,7 +129,7 @@ func WebMCPRequestSizeMiddleware(next http.Handler) http.Handler { switch { case r.Method == http.MethodPost && r.URL.Path == "/webmcp/invoke": r.Body = http.MaxBytesReader(w, r.Body, maxWebMCPRequestBytes) - case r.Method == http.MethodPut && r.URL.Path == "/webmcp/custom-tools": + case r.Method == http.MethodPost && r.URL.Path == "/webmcp/custom-tools": r.Body = http.MaxBytesReader(w, r.Body, maxCustomWebMCPRequestBytes) } next.ServeHTTP(w, r) @@ -154,7 +155,10 @@ func StrictResponseErrorHandler(w http.ResponseWriter, r *http.Request, err erro } func isWebMCPRequest(r *http.Request) bool { - return r.URL.Path == "/webmcp/tools" || r.URL.Path == "/webmcp/invoke" || r.URL.Path == "/webmcp/custom-tools" + return r.URL.Path == "/webmcp/tools" || + r.URL.Path == "/webmcp/invoke" || + r.URL.Path == "/webmcp/custom-tools" || + strings.HasPrefix(r.URL.Path, "/webmcp/custom-tools/") } func writeStrictError(w http.ResponseWriter, status int, message string) { diff --git a/server/cmd/api/api/middleware_test.go b/server/cmd/api/api/middleware_test.go index abce8e21..53c258b3 100644 --- a/server/cmd/api/api/middleware_test.go +++ b/server/cmd/api/api/middleware_test.go @@ -90,7 +90,7 @@ func TestWebMCPRequestSizeMiddlewareRejectsOversizedInvokeBody(t *testing.T) { func TestWebMCPRequestSizeMiddlewareRejectsOversizedCustomToolBody(t *testing.T) { body := strings.NewReader(strings.Repeat("x", maxCustomWebMCPRequestBytes+1)) - request := httptest.NewRequest(http.MethodPut, "/webmcp/custom-tools", body) + request := httptest.NewRequest(http.MethodPost, "/webmcp/custom-tools", body) recorder := httptest.NewRecorder() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, err := io.ReadAll(r.Body) diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 88fefe17..4e9f354e 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -20,7 +20,7 @@ const ( maxWebMCPRequestBytes = maxWebMCPInputBytes + (4 << 10) ) -func (s *ApiService) GetWebMCPTools(ctx context.Context, _ oapi.GetWebMCPToolsRequestObject) (oapi.GetWebMCPToolsResponseObject, error) { +func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPToolsRequestObject) (oapi.GetWebMCPToolsResponseObject, error) { tools, err := s.webmcp.Tools(ctx) if err != nil { if errors.Is(err, webmcpclient.ErrNoPageTarget) { @@ -30,17 +30,37 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, _ oapi.GetWebMCPToolsRe return oapi.GetWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to discover WebMCP tools"}}, nil } + excludeCustom := request.Params.ExcludeCustom != nil && *request.Params.ExcludeCustom + customDefinitions := make(map[string]oapi.CustomWebMCPDefinition) + if s.browserRepl != nil { + customDefinitions = s.browserRepl.customToolsSnapshot() + } + responseTools := make([]oapi.WebMCPTool, 0, len(tools)) for _, tool := range tools { + if excludeCustom && tool.CustomID != "" { + continue + } inputSchema := tool.InputSchema if inputSchema == nil { inputSchema = make(map[string]any) } - responseTool := oapi.WebMCPTool{ - ToolRef: tool.Ref, + metadata := oapi.WebMCPToolMetadata{ Name: tool.Name, Description: tool.Description, InputSchema: inputSchema, + } + if tool.Annotations != nil { + metadata.Annotations = &oapi.WebMCPToolAnnotations{ + ReadOnlyHint: boolPointer(tool.Annotations.ReadOnly), + UntrustedContentHint: boolPointer(tool.Annotations.UntrustedContent), + ConsequentialHint: boolPointer(tool.Annotations.Consequential), + Autosubmit: boolPointer(tool.Annotations.Autosubmit), + } + } + responseTool := oapi.WebMCPTool{ + ToolRef: tool.Ref, + Tool: metadata, Source: oapi.WebMCPToolSource{ WindowId: tool.Source.WindowID, TabId: tool.Source.TabID, @@ -54,12 +74,22 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, _ oapi.GetWebMCPToolsRe Url: tool.Source.Frame.URL, } } - if tool.Annotations != nil { - responseTool.Annotations = &oapi.WebMCPToolAnnotations{ - ReadOnly: tool.Annotations.ReadOnly, - UntrustedContent: tool.Annotations.UntrustedContent, - Consequential: tool.Annotations.Consequential, - Autosubmit: tool.Annotations.Autosubmit, + if tool.CustomID != "" { + definition, ok := customDefinitions[tool.CustomID] + if !ok { + continue + } + responseTool.Source.Custom = &oapi.WebMCPCustomToolSource{ + Id: definition.Id, + Namespace: definition.Namespace, + } + responseTool.Tool = oapi.WebMCPToolMetadata{ + Name: definition.Tool.Name, + Title: definition.Tool.Title, + Description: definition.Tool.Description, + InputSchema: definition.Tool.InputSchema, + OutputSchema: &definition.Tool.OutputSchema, + Annotations: definition.Tool.Annotations, } } responseTools = append(responseTools, responseTool) @@ -67,6 +97,8 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, _ oapi.GetWebMCPToolsRe return oapi.GetWebMCPTools200JSONResponse{Tools: responseTools}, nil } +func boolPointer(value bool) *bool { return &value } + func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWebMCPToolRequestObject) (oapi.InvokeWebMCPToolResponseObject, error) { if request.Body == nil { return oapi.InvokeWebMCPTool400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 18c3b064..953fd139 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -61,8 +61,43 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { require.Equal(t, "Store", tool.Source.PageTitle) require.Equal(t, 7, tool.Source.Frame.FrameId) require.Equal(t, "https://payments.example/element", tool.Source.Frame.Url) - require.Empty(t, tool.InputSchema) - require.True(t, tool.Annotations.Consequential) + require.Empty(t, tool.Tool.InputSchema) + require.True(t, *tool.Tool.Annotations.ConsequentialHint) +} + +func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { + outputSchema := map[string]any{"type": "object"} + manager := newBrowserReplManager() + manager.setCustomTools([]oapi.CustomWebMCPDefinition{{ + Id: "ct_0123456789abcdef", + Namespace: "stripe.com", + Kind: "cdp", + Match: oapi.CustomWebMCPMatch{UrlPatterns: []string{"https://checkout.stripe.com/*"}}, + Tool: oapi.CustomWebMCPToolMetadata{ + Name: "fill_payment_form", Description: "Fill payment fields", + InputSchema: map[string]any{"type": "object"}, OutputSchema: outputSchema, + }, + }}) + client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ + Ref: "wmcp_custom", Name: "fill_payment_form", CustomID: "ct_0123456789abcdef", + Source: webmcpclient.ToolSource{WindowID: 1, TabID: 2, PageTitle: "Checkout", PageURL: "https://checkout.stripe.com/"}, + }}} + service := &ApiService{webmcp: client, browserRepl: manager} + + response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) + require.NoError(t, err) + tools := response.(oapi.GetWebMCPTools200JSONResponse).Tools + require.Len(t, tools, 1) + require.Equal(t, "stripe.com", tools[0].Source.Custom.Namespace) + require.Equal(t, "ct_0123456789abcdef", tools[0].Source.Custom.Id) + require.Equal(t, outputSchema, *tools[0].Tool.OutputSchema) + + exclude := true + response, err = service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{ + Params: oapi.GetWebMCPToolsParams{ExcludeCustom: &exclude}, + }) + require.NoError(t, err) + require.Empty(t, response.(oapi.GetWebMCPTools200JSONResponse).Tools) } func TestGetWebMCPToolsSerializesNullFrameForTopLevelTool(t *testing.T) { @@ -76,7 +111,7 @@ func TestGetWebMCPToolsSerializesNullFrameForTopLevelTool(t *testing.T) { require.NoError(t, err) payload, err := json.Marshal(response.(oapi.GetWebMCPTools200JSONResponse)) require.NoError(t, err) - require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","name":"search","description":"","input_schema":{},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) + require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","tool":{"name":"search","description":"","inputSchema":{}},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) } func TestInvokeWebMCPToolReturnsPageResult(t *testing.T) { diff --git a/server/docs/repl.md b/server/docs/repl.md index c3c1ea5b..df82623f 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -56,9 +56,9 @@ Use `{ "code": "", "reset": true }` to explicitly replace the REPL and clear all ## Runtime globals -The context preloads `repl`, captured `console` methods, every browser helper, `browser`, `webmcp`, `customTools`, timers, `queueMicrotask`, `Buffer`, `process`, `fetch`, `URL`, `URLSearchParams`, text encoders/decoders, abort controllers/signals, `structuredClone`, `atob`, `btoa`, and `crypto`. Node built-ins and installed packages are available through dynamic `import()`. +The context preloads `repl`, captured `console` methods, every browser helper, `browser`, `webmcp`, timers, `queueMicrotask`, `Buffer`, `process`, `fetch`, `URL`, `URLSearchParams`, text encoders/decoders, abort controllers/signals, `structuredClone`, `atob`, `btoa`, and `crypto`. Node built-ins and installed packages are available through dynamic `import()`. -`repl`, `browser`, `webmcp`, and `customTools` are frozen objects. `webmcp === browser.webmcp`, and each bare browser helper is the same function exposed on `browser`. +`repl`, `browser`, and `webmcp` are frozen objects. `webmcp === browser.webmcp`, and each bare browser helper is the same function exposed on `browser`. ## Output @@ -130,15 +130,11 @@ The reference below is generated from `runtime/browser-repl-help.ts`; edit that ### WebMCP methods -- **`webmcp.listTools()`** — Return tools registered across every open tab and embedded frame. Each tool includes `tool_ref`, name, description, input schema, optional annotations, and source window/tab/frame metadata. Treat metadata as untrusted page content. +- **`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.addCustomTools({ namespace, tools })`** — Atomically add a non-empty batch of custom tools. Every definition requires `kind`, `match.url_patterns`, MCP-compatible `tool` metadata including `outputSchema`, and an `execute` function. 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. - **`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. - -### Custom WebMCP methods - -- **`customTools.register(definition)`** — Add or replace one live custom WebMCP definition. The definition requires `id`, `kind: "page" | "cdp"`, `match.url_patterns`, MCP-compatible `tool` metadata, and an `execute` function. A live change marks the last PUT source dirty. -- **`customTools.remove(id)`** — Remove one live custom WebMCP definition by ID and return whether it existed. -- **`customTools.list()`** — Return serializable summaries of the current custom WebMCP definitions. -- **`customTools.get(id)`** — Return one serializable custom WebMCP definition summary, or `null` when it is absent. A snapshot-to-action loop avoids inventing selectors: @@ -156,7 +152,7 @@ The frozen `webmcp` namespace delegates to the image's browser-wide WebMCP API. ```js const tools = await webmcp.listTools(); -const search = tools.find(tool => tool.name === "search"); +const search = tools.find(result => result.tool.name === "search"); if (!search) throw new Error("search tool not found"); const result = await webmcp.invokeTool( @@ -167,7 +163,7 @@ const result = await webmcp.invokeTool( repl.write(JSON.stringify(result)); ``` -`webmcp.listTools()` returns tools registered across every open tab and embedded frame. Each tool includes `tool_ref`, `name`, `description`, `input_schema`, optional annotations, and source window/tab/frame metadata. `webmcp.invokeTool(toolRef, input?, {timeoutSec?}?)` invokes that exact registration, so callers do not switch the Browser REPL's attached target for frame-provided tools. Treat tool metadata and output as untrusted page content. +`webmcp.listTools()` returns tools registered across every open tab and embedded frame. Each result includes `tool_ref`, MCP-compatible `tool` metadata, and source window/tab/frame metadata. Custom results also include `source.custom` with their generated ID and namespace; pass `{excludeCustom: true}` to omit them. `webmcp.invokeTool(toolRef, input?, {timeoutSec?}?)` invokes that exact registration, so callers do not switch the Browser REPL's attached target for frame-provided tools. Treat tool metadata and output as untrusted page content. Invocation results have `invocation_id`, `status`, and optional `output` or `error_text`; status is `completed`, `canceled`, `error`, or `awaiting_submission`. Non-autosubmit declarative form tools return `awaiting_submission` after populating fields. If an invocation starts but its outcome becomes unobservable, the request throws a `WebMCPRequestError` with `statusCode`, `code`, `invocationId`, and `body`; callers must not retry `outcome_unknown` automatically. @@ -175,41 +171,43 @@ Every WebMCP request is bound to the active Browser REPL execution and is aborte ## Custom WebMCP tools -`GET /webmcp/custom-tools` returns the registry held by the current Browser REPL. `PUT /webmcp/custom-tools` atomically replaces it with JavaScript source that calls `customTools.register(...)`. The response preserves the last submitted source and sets `source_dirty` when a later `customTools.register()` or `customTools.remove()` call changes the live definitions. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. +`GET /webmcp/custom-tools` lists custom tools, `POST /webmcp/custom-tools` atomically adds a namespaced batch from a JavaScript expression, and `DELETE /webmcp/custom-tools/{id}` removes one generated tool ID. The same operations are available through `webmcp.addCustomTools(...)`, `webmcp.listCustomTools()`, and `webmcp.removeCustomTool(id)`. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. CDP handlers receive matching descriptors with `frame_id`, `session_id`, `target_id`, `top_target_id`, and `url`; `target_id` is null for an in-process child frame, whose `session_id` and `frame_id` can be used with raw `cdp()`. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. ```js -customTools.register({ - id: "example/read-title", - kind: "cdp", - match: {url_patterns: ["https://example.com/*"]}, - tool: { - name: "read_title", - description: "Read the current page title.", - inputSchema: {type: "object", additionalProperties: false}, - annotations: {readOnlyHint: true}, - }, - outputSchema: { - type: "object", - properties: {title: {type: "string"}}, - required: ["title"], - additionalProperties: false, - }, - execute: async (_input, {matches}) => ({ - title: await js(() => document.title, {targetId: matches[0].top_target_id}), - }), +const [readTitle] = await webmcp.addCustomTools({ + namespace: "example.com", + tools: [{ + kind: "cdp", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_title", + description: "Read the current page title.", + inputSchema: {type: "object", additionalProperties: false}, + outputSchema: { + type: "object", + properties: {title: {type: "string"}}, + required: ["title"], + additionalProperties: false, + }, + annotations: {readOnlyHint: true}, + }, + execute: async (_input, {matches}) => ({ + title: await js(() => document.title, {targetId: matches[0].top_target_id}), + }), + }], }); ``` -The registry validates MCP metadata with `ToolSchema` and compiles input and optional output schemas with `AjvJsonSchemaValidator`. CDP-backed handlers validate output before resolving their page proxy; page-backed handlers follow native imperative WebMCP result handling because Chromium does not currently accept `outputSchema`. Both packages are pinned image dependencies and may also be imported directly: +The registry validates MCP metadata with `ToolSchema` and requires and compiles both input and output schemas with `AjvJsonSchemaValidator`. CDP-backed handlers validate output before resolving their page proxy; page-backed handlers follow native imperative WebMCP result handling because Chromium does not currently accept `outputSchema`. Both packages are pinned image dependencies and may also be imported directly: ```js var {ToolSchema} = await import("@modelcontextprotocol/core"); var {AjvJsonSchemaValidator} = await import("@modelcontextprotocol/server/validators/ajv"); ``` -Replacing or removing a definition stops future discovery without canceling active invocations. Registrations are continuously reconciled as frames are created, navigate, and detach. A graceful REPL reset removes registrations, clears the registry, and changes `repl_id`. After an abrupt process death, stale page registrations can remain visible until the next Browser REPL starts and performs its one-time empty-registry cleanup, or until their documents navigate; those stale registrations have no live handler and invocations time out. +Removing a definition stops future discovery without canceling active invocations. Updating a tool requires removal followed by addition, which assigns a new ID and new live tool references. Registrations are continuously reconciled as frames are created, navigate, and detach. A graceful REPL reset removes registrations, clears the registry, and changes `repl_id`. After an abrupt process death, stale page registrations can remain visible until the next Browser REPL starts and performs its one-time empty-registry cleanup, or until their documents navigate; those stale registrations have no live handler and invocations time out. ## Patchright and Playwright Core diff --git a/server/e2e/e2e_webmcp_test.go b/server/e2e/e2e_webmcp_test.go index 7a21a859..ac73d4d9 100644 --- a/server/e2e/e2e_webmcp_test.go +++ b/server/e2e/e2e_webmcp_test.go @@ -52,7 +52,7 @@ func testWebMCPDeclarative(t *testing.T, ctx context.Context, client *instanceoa var tool instanceoapi.WebMCPTool require.EventuallyWithT(t, func(collect *assert.CollectT) { - rsp, err := client.GetWebMCPToolsWithResponse(ctx) + rsp, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) if !assert.NoError(collect, err) { return } @@ -60,7 +60,7 @@ func testWebMCPDeclarative(t *testing.T, ctx context.Context, client *instanceoa return } for _, candidate := range rsp.JSON200.Tools { - if candidate.Name == "reserve_table" && candidate.Source.PageUrl == pageURL { + if candidate.Tool.Name == "reserve_table" && candidate.Source.PageUrl == pageURL { tool = candidate return } @@ -71,10 +71,10 @@ func testWebMCPDeclarative(t *testing.T, ctx context.Context, client *instanceoa require.NoError(t, err) t.Logf("GET /webmcp/tools: %s", toolJSON) require.NotEmpty(t, tool.ToolRef) - require.Equal(t, "Reserve a table with guest details and seating preferences.", tool.Description) - require.Equal(t, "object", tool.InputSchema["type"]) - properties, ok := tool.InputSchema["properties"].(map[string]any) - require.True(t, ok, "schema properties: %#v", tool.InputSchema) + require.Equal(t, "Reserve a table with guest details and seating preferences.", tool.Tool.Description) + require.Equal(t, "object", tool.Tool.InputSchema["type"]) + properties, ok := tool.Tool.InputSchema["properties"].(map[string]any) + require.True(t, ok, "schema properties: %#v", tool.Tool.InputSchema) require.Len(t, properties, 4) for _, field := range []struct { name, kind, description string @@ -96,7 +96,7 @@ func testWebMCPDeclarative(t *testing.T, ctx context.Context, client *instanceoa partySize := properties["party_size"].(map[string]any) require.EqualValues(t, 1, partySize["minimum"]) require.EqualValues(t, 8, partySize["maximum"]) - require.ElementsMatch(t, []string{"name", "date", "party_size", "seating"}, tool.InputSchema["required"]) + require.ElementsMatch(t, []string{"name", "date", "party_size", "seating"}, tool.Tool.InputSchema["required"]) require.ElementsMatch(t, []string{"dining_room", "terrace", "booth"}, properties["seating"].(map[string]any)["enum"]) require.Equal(t, test.title, tool.Source.PageTitle) require.Positive(t, tool.Source.TabId) @@ -108,8 +108,9 @@ func testWebMCPDeclarative(t *testing.T, ctx context.Context, client *instanceoa } else { require.Nil(t, tool.Source.Frame) } - require.NotNil(t, tool.Annotations) - require.True(t, tool.Annotations.Autosubmit) + require.NotNil(t, tool.Tool.Annotations) + require.NotNil(t, tool.Tool.Annotations.Autosubmit) + require.True(t, *tool.Tool.Annotations.Autosubmit) timeout := 10 rsp, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ diff --git a/server/lib/events/category_gen.go b/server/lib/events/category_gen.go index c5a377a3..5e276ca1 100644 --- a/server/lib/events/category_gen.go +++ b/server/lib/events/category_gen.go @@ -51,6 +51,7 @@ func CategoryForType(eventType string) (oapi.TelemetryEventCategory, bool) { } var categoryByOperationID = map[string]oapi.TelemetryEventCategory{ + "AddCustomWebMCPTools": oapi.TelemetryEventCategory("control"), "BatchComputerAction": oapi.TelemetryEventCategory("control"), "ChromiumConfigure": oapi.TelemetryEventCategory("platform"), "ClickMouse": oapi.TelemetryEventCategory("control"), @@ -67,11 +68,11 @@ var categoryByOperationID = map[string]oapi.TelemetryEventCategory{ "ExecuteBrowserRepl": oapi.TelemetryEventCategory("control"), "ExecutePlaywrightCode": oapi.TelemetryEventCategory("control"), "FileInfo": oapi.TelemetryEventCategory("platform"), - "GetCustomWebMCPTools": oapi.TelemetryEventCategory("control"), "GetMousePosition": oapi.TelemetryEventCategory("control"), "GetTelemetry": oapi.TelemetryEventCategory("platform"), "GetWebMCPTools": oapi.TelemetryEventCategory("control"), "InvokeWebMCPTool": oapi.TelemetryEventCategory("control"), + "ListCustomWebMCPTools": oapi.TelemetryEventCategory("control"), "ListFiles": oapi.TelemetryEventCategory("platform"), "ListRecorders": oapi.TelemetryEventCategory("platform"), "LogsStream": oapi.TelemetryEventCategory("platform"), @@ -94,7 +95,7 @@ var categoryByOperationID = map[string]oapi.TelemetryEventCategory{ "PutTelemetry": oapi.TelemetryEventCategory("platform"), "ReadClipboard": oapi.TelemetryEventCategory("control"), "ReadFile": oapi.TelemetryEventCategory("platform"), - "ReplaceCustomWebMCPTools": oapi.TelemetryEventCategory("control"), + "RemoveCustomWebMCPTool": oapi.TelemetryEventCategory("control"), "Scroll": oapi.TelemetryEventCategory("control"), "SetCursor": oapi.TelemetryEventCategory("control"), "SetFilePermissions": oapi.TelemetryEventCategory("platform"), diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index ee5c488a..e26d3911 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -3086,6 +3086,14 @@ func (e StreamTelemetryEventsParamsReplay) Valid() bool { } } +// AddCustomWebMCPToolsRequest defines model for AddCustomWebMCPToolsRequest. +type AddCustomWebMCPToolsRequest struct { + Namespace string `json:"namespace"` + + // Source JavaScript expression that evaluates to a non-empty array of custom tool definitions. + Source string `json:"source"` +} + // BatchComputerActionRequest A batch of computer actions to execute sequentially. type BatchComputerActionRequest struct { // Actions Ordered list of actions to execute. Execution stops on the first error. @@ -6260,21 +6268,11 @@ type CreateDirectoryRequest struct { // CustomWebMCPDefinition defines model for CustomWebMCPDefinition. type CustomWebMCPDefinition struct { - Id string `json:"id"` - Kind string `json:"kind"` - Match CustomWebMCPMatch `json:"match"` - OutputSchema *map[string]interface{} `json:"output_schema,omitempty"` - Revision int `json:"revision"` - - // Tool MCP-compatible tool metadata as submitted to customTools.register(). - Tool map[string]interface{} `json:"tool"` -} - -// CustomWebMCPInstallation defines model for CustomWebMCPInstallation. -type CustomWebMCPInstallation struct { - DefinitionIds []string `json:"definition_ids"` - Errors []string `json:"errors"` - TargetId string `json:"target_id"` + Id string `json:"id"` + Kind string `json:"kind"` + Match CustomWebMCPMatch `json:"match"` + Namespace string `json:"namespace"` + Tool CustomWebMCPToolMetadata `json:"tool"` } // CustomWebMCPMatch defines model for CustomWebMCPMatch. @@ -6282,22 +6280,20 @@ type CustomWebMCPMatch struct { UrlPatterns []string `json:"url_patterns"` } -// CustomWebMCPRegistry defines model for CustomWebMCPRegistry. -type CustomWebMCPRegistry struct { - Installations []CustomWebMCPInstallation `json:"installations"` - ReplId string `json:"repl_id"` - Revision int `json:"revision"` - Source string `json:"source"` - - // SourceDirty True when customTools.register() or customTools.remove() changed the live registry after the last PUT. - SourceDirty bool `json:"source_dirty"` - Tools []CustomWebMCPDefinition `json:"tools"` +// CustomWebMCPToolMetadata defines model for CustomWebMCPToolMetadata. +type CustomWebMCPToolMetadata struct { + // Annotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` + Description string `json:"description"` + InputSchema map[string]interface{} `json:"inputSchema"` + Name string `json:"name"` + OutputSchema map[string]interface{} `json:"outputSchema"` + Title *string `json:"title,omitempty"` } -// CustomWebMCPRegistryRequest defines model for CustomWebMCPRegistryRequest. -type CustomWebMCPRegistryRequest struct { - // Source JavaScript evaluated in the existing Browser REPL with customTools and all Browser REPL globals available. - Source string `json:"source"` +// CustomWebMCPToolsResponse defines model for CustomWebMCPToolsResponse. +type CustomWebMCPToolsResponse struct { + Tools []CustomWebMCPDefinition `json:"tools"` } // DeletePathRequest defines model for DeletePathRequest. @@ -6904,6 +6900,12 @@ type TypeTextRequest struct { TypoChance *float32 `json:"typo_chance,omitempty"` } +// WebMCPCustomToolSource defines model for WebMCPCustomToolSource. +type WebMCPCustomToolSource struct { + Id string `json:"id"` + Namespace string `json:"namespace"` +} + // WebMCPInvocationFailure defines model for WebMCPInvocationFailure. type WebMCPInvocationFailure struct { Code WebMCPInvocationFailureCode `json:"code"` @@ -6939,23 +6941,22 @@ type WebMCPInvokeRequest struct { // WebMCPTool defines model for WebMCPTool. type WebMCPTool struct { - // Annotations Page-provided behavioral hints. These values are untrusted and are not enforced by Kernel. - Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` - Description string `json:"description"` - InputSchema map[string]interface{} `json:"input_schema"` - Name string `json:"name"` - Source WebMCPToolSource `json:"source"` + Source WebMCPToolSource `json:"source"` + Tool WebMCPToolMetadata `json:"tool"` // ToolRef Opaque reference for invoking this exact live registration. It becomes invalid when its document or browser process is replaced. ToolRef string `json:"tool_ref"` } -// WebMCPToolAnnotations Page-provided behavioral hints. These values are untrusted and are not enforced by Kernel. +// WebMCPToolAnnotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. type WebMCPToolAnnotations struct { - Autosubmit bool `json:"autosubmit"` - Consequential bool `json:"consequential"` - ReadOnly bool `json:"read_only"` - UntrustedContent bool `json:"untrusted_content"` + Autosubmit *bool `json:"autosubmit,omitempty"` + ConsequentialHint *bool `json:"consequentialHint,omitempty"` + DestructiveHint *bool `json:"destructiveHint,omitempty"` + IdempotentHint *bool `json:"idempotentHint,omitempty"` + OpenWorldHint *bool `json:"openWorldHint,omitempty"` + ReadOnlyHint *bool `json:"readOnlyHint,omitempty"` + UntrustedContentHint *bool `json:"untrustedContentHint,omitempty"` } // WebMCPToolFrame defines model for WebMCPToolFrame. @@ -6967,8 +6968,21 @@ type WebMCPToolFrame struct { Url string `json:"url"` } +// WebMCPToolMetadata defines model for WebMCPToolMetadata. +type WebMCPToolMetadata struct { + // Annotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` + Description string `json:"description"` + InputSchema map[string]interface{} `json:"inputSchema"` + Name string `json:"name"` + OutputSchema *map[string]interface{} `json:"outputSchema,omitempty"` + Title *string `json:"title,omitempty"` +} + // WebMCPToolSource defines model for WebMCPToolSource. type WebMCPToolSource struct { + Custom *WebMCPCustomToolSource `json:"custom,omitempty"` + // Frame Embedded frame that registered the tool, or null when the top-level page registered it. Frame *WebMCPToolFrame `json:"frame"` @@ -7163,6 +7177,12 @@ type StreamTelemetryEventsParams struct { // StreamTelemetryEventsParamsReplay defines parameters for StreamTelemetryEvents. type StreamTelemetryEventsParamsReplay string +// GetWebMCPToolsParams defines parameters for GetWebMCPTools. +type GetWebMCPToolsParams struct { + // ExcludeCustom Exclude custom tools when true. + ExcludeCustom *bool `form:"exclude_custom,omitempty" json:"exclude_custom,omitempty"` +} + // PatchChromiumFlagsJSONRequestBody defines body for PatchChromiumFlags for application/json ContentType. type PatchChromiumFlagsJSONRequestBody PatchChromiumFlagsJSONBody @@ -7280,8 +7300,8 @@ type PutTelemetryJSONRequestBody = BrowserTelemetryConfig // PublishTelemetryEventJSONRequestBody defines body for PublishTelemetryEvent for application/json ContentType. type PublishTelemetryEventJSONRequestBody = PublishEventRequest -// ReplaceCustomWebMCPToolsJSONRequestBody defines body for ReplaceCustomWebMCPTools for application/json ContentType. -type ReplaceCustomWebMCPToolsJSONRequestBody = CustomWebMCPRegistryRequest +// AddCustomWebMCPToolsJSONRequestBody defines body for AddCustomWebMCPTools for application/json ContentType. +type AddCustomWebMCPToolsJSONRequestBody = AddCustomWebMCPToolsRequest // InvokeWebMCPToolJSONRequestBody defines body for InvokeWebMCPTool for application/json ContentType. type InvokeWebMCPToolJSONRequestBody = WebMCPInvokeRequest @@ -9966,13 +9986,16 @@ type ClientInterface interface { // StreamTelemetryEvents request StreamTelemetryEvents(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetCustomWebMCPTools request - GetCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListCustomWebMCPTools request + ListCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AddCustomWebMCPToolsWithBody request with any body + AddCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // ReplaceCustomWebMCPToolsWithBody request with any body - ReplaceCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + AddCustomWebMCPTools(ctx context.Context, body AddCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - ReplaceCustomWebMCPTools(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // RemoveCustomWebMCPTool request + RemoveCustomWebMCPTool(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // InvokeWebMCPToolWithBody request with any body InvokeWebMCPToolWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9980,7 +10003,7 @@ type ClientInterface interface { InvokeWebMCPTool(ctx context.Context, body InvokeWebMCPToolJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // GetWebMCPTools request - GetWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + GetWebMCPTools(ctx context.Context, params *GetWebMCPToolsParams, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) PatchChromiumFlagsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -11075,8 +11098,8 @@ func (c *Client) StreamTelemetryEvents(ctx context.Context, params *StreamTeleme return c.Client.Do(req) } -func (c *Client) GetCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetCustomWebMCPToolsRequest(c.Server) +func (c *Client) ListCustomWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListCustomWebMCPToolsRequest(c.Server) if err != nil { return nil, err } @@ -11087,8 +11110,8 @@ func (c *Client) GetCustomWebMCPTools(ctx context.Context, reqEditors ...Request return c.Client.Do(req) } -func (c *Client) ReplaceCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewReplaceCustomWebMCPToolsRequestWithBody(c.Server, contentType, body) +func (c *Client) AddCustomWebMCPToolsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddCustomWebMCPToolsRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -11099,8 +11122,20 @@ func (c *Client) ReplaceCustomWebMCPToolsWithBody(ctx context.Context, contentTy return c.Client.Do(req) } -func (c *Client) ReplaceCustomWebMCPTools(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewReplaceCustomWebMCPToolsRequest(c.Server, body) +func (c *Client) AddCustomWebMCPTools(ctx context.Context, body AddCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAddCustomWebMCPToolsRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RemoveCustomWebMCPTool(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRemoveCustomWebMCPToolRequest(c.Server, id) if err != nil { return nil, err } @@ -11135,8 +11170,8 @@ func (c *Client) InvokeWebMCPTool(ctx context.Context, body InvokeWebMCPToolJSON return c.Client.Do(req) } -func (c *Client) GetWebMCPTools(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetWebMCPToolsRequest(c.Server) +func (c *Client) GetWebMCPTools(ctx context.Context, params *GetWebMCPToolsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWebMCPToolsRequest(c.Server, params) if err != nil { return nil, err } @@ -13492,8 +13527,8 @@ func NewStreamTelemetryEventsRequest(server string, params *StreamTelemetryEvent return req, nil } -// NewGetCustomWebMCPToolsRequest generates requests for GetCustomWebMCPTools -func NewGetCustomWebMCPToolsRequest(server string) (*http.Request, error) { +// NewListCustomWebMCPToolsRequest generates requests for ListCustomWebMCPTools +func NewListCustomWebMCPToolsRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -13519,19 +13554,19 @@ func NewGetCustomWebMCPToolsRequest(server string) (*http.Request, error) { return req, nil } -// NewReplaceCustomWebMCPToolsRequest calls the generic ReplaceCustomWebMCPTools builder with application/json body -func NewReplaceCustomWebMCPToolsRequest(server string, body ReplaceCustomWebMCPToolsJSONRequestBody) (*http.Request, error) { +// NewAddCustomWebMCPToolsRequest calls the generic AddCustomWebMCPTools builder with application/json body +func NewAddCustomWebMCPToolsRequest(server string, body AddCustomWebMCPToolsJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewReplaceCustomWebMCPToolsRequestWithBody(server, "application/json", bodyReader) + return NewAddCustomWebMCPToolsRequestWithBody(server, "application/json", bodyReader) } -// NewReplaceCustomWebMCPToolsRequestWithBody generates requests for ReplaceCustomWebMCPTools with any type of body -func NewReplaceCustomWebMCPToolsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewAddCustomWebMCPToolsRequestWithBody generates requests for AddCustomWebMCPTools with any type of body +func NewAddCustomWebMCPToolsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -13549,7 +13584,7 @@ func NewReplaceCustomWebMCPToolsRequestWithBody(server string, contentType strin return nil, err } - req, err := http.NewRequest("PUT", queryURL.String(), body) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } @@ -13559,6 +13594,40 @@ func NewReplaceCustomWebMCPToolsRequestWithBody(server string, contentType strin return req, nil } +// NewRemoveCustomWebMCPToolRequest generates requests for RemoveCustomWebMCPTool +func NewRemoveCustomWebMCPToolRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/webmcp/custom-tools/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewInvokeWebMCPToolRequest calls the generic InvokeWebMCPTool builder with application/json body func NewInvokeWebMCPToolRequest(server string, body InvokeWebMCPToolJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -13600,7 +13669,7 @@ func NewInvokeWebMCPToolRequestWithBody(server string, contentType string, body } // NewGetWebMCPToolsRequest generates requests for GetWebMCPTools -func NewGetWebMCPToolsRequest(server string) (*http.Request, error) { +func NewGetWebMCPToolsRequest(server string, params *GetWebMCPToolsParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -13618,6 +13687,28 @@ func NewGetWebMCPToolsRequest(server string) (*http.Request, error) { return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.ExcludeCustom != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "exclude_custom", *params.ExcludeCustom, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "boolean", Format: ""}); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -13909,13 +14000,16 @@ type ClientWithResponsesInterface interface { // StreamTelemetryEventsWithResponse request StreamTelemetryEventsWithResponse(ctx context.Context, params *StreamTelemetryEventsParams, reqEditors ...RequestEditorFn) (*StreamTelemetryEventsResponse, error) - // GetCustomWebMCPToolsWithResponse request - GetCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCustomWebMCPToolsResponse, error) + // ListCustomWebMCPToolsWithResponse request + ListCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListCustomWebMCPToolsResponse, error) + + // AddCustomWebMCPToolsWithBodyWithResponse request with any body + AddCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddCustomWebMCPToolsResponse, error) - // ReplaceCustomWebMCPToolsWithBodyWithResponse request with any body - ReplaceCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) + AddCustomWebMCPToolsWithResponse(ctx context.Context, body AddCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*AddCustomWebMCPToolsResponse, error) - ReplaceCustomWebMCPToolsWithResponse(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) + // RemoveCustomWebMCPToolWithResponse request + RemoveCustomWebMCPToolWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RemoveCustomWebMCPToolResponse, error) // InvokeWebMCPToolWithBodyWithResponse request with any body InvokeWebMCPToolWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*InvokeWebMCPToolResponse, error) @@ -13923,7 +14017,7 @@ type ClientWithResponsesInterface interface { InvokeWebMCPToolWithResponse(ctx context.Context, body InvokeWebMCPToolJSONRequestBody, reqEditors ...RequestEditorFn) (*InvokeWebMCPToolResponse, error) // GetWebMCPToolsWithResponse request - GetWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetWebMCPToolsResponse, error) + GetWebMCPToolsWithResponse(ctx context.Context, params *GetWebMCPToolsParams, reqEditors ...RequestEditorFn) (*GetWebMCPToolsResponse, error) } type PatchChromiumFlagsResponse struct { @@ -15306,15 +15400,15 @@ func (r StreamTelemetryEventsResponse) StatusCode() int { return 0 } -type GetCustomWebMCPToolsResponse struct { +type ListCustomWebMCPToolsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CustomWebMCPRegistry + JSON200 *CustomWebMCPToolsResponse JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r GetCustomWebMCPToolsResponse) Status() string { +func (r ListCustomWebMCPToolsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -15322,23 +15416,47 @@ func (r GetCustomWebMCPToolsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetCustomWebMCPToolsResponse) StatusCode() int { +func (r ListCustomWebMCPToolsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ReplaceCustomWebMCPToolsResponse struct { +type AddCustomWebMCPToolsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *CustomWebMCPRegistry + JSON201 *CustomWebMCPToolsResponse JSON400 *BadRequestError + JSON409 *ConflictError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r AddCustomWebMCPToolsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AddCustomWebMCPToolsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RemoveCustomWebMCPToolResponse struct { + Body []byte + HTTPResponse *http.Response + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r ReplaceCustomWebMCPToolsResponse) Status() string { +func (r RemoveCustomWebMCPToolResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -15346,7 +15464,7 @@ func (r ReplaceCustomWebMCPToolsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ReplaceCustomWebMCPToolsResponse) StatusCode() int { +func (r RemoveCustomWebMCPToolResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -16189,30 +16307,39 @@ func (c *ClientWithResponses) StreamTelemetryEventsWithResponse(ctx context.Cont return ParseStreamTelemetryEventsResponse(rsp) } -// GetCustomWebMCPToolsWithResponse request returning *GetCustomWebMCPToolsResponse -func (c *ClientWithResponses) GetCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetCustomWebMCPToolsResponse, error) { - rsp, err := c.GetCustomWebMCPTools(ctx, reqEditors...) +// ListCustomWebMCPToolsWithResponse request returning *ListCustomWebMCPToolsResponse +func (c *ClientWithResponses) ListCustomWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListCustomWebMCPToolsResponse, error) { + rsp, err := c.ListCustomWebMCPTools(ctx, reqEditors...) if err != nil { return nil, err } - return ParseGetCustomWebMCPToolsResponse(rsp) + return ParseListCustomWebMCPToolsResponse(rsp) } -// ReplaceCustomWebMCPToolsWithBodyWithResponse request with arbitrary body returning *ReplaceCustomWebMCPToolsResponse -func (c *ClientWithResponses) ReplaceCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) { - rsp, err := c.ReplaceCustomWebMCPToolsWithBody(ctx, contentType, body, reqEditors...) +// AddCustomWebMCPToolsWithBodyWithResponse request with arbitrary body returning *AddCustomWebMCPToolsResponse +func (c *ClientWithResponses) AddCustomWebMCPToolsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddCustomWebMCPToolsResponse, error) { + rsp, err := c.AddCustomWebMCPToolsWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseReplaceCustomWebMCPToolsResponse(rsp) + return ParseAddCustomWebMCPToolsResponse(rsp) } -func (c *ClientWithResponses) ReplaceCustomWebMCPToolsWithResponse(ctx context.Context, body ReplaceCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*ReplaceCustomWebMCPToolsResponse, error) { - rsp, err := c.ReplaceCustomWebMCPTools(ctx, body, reqEditors...) +func (c *ClientWithResponses) AddCustomWebMCPToolsWithResponse(ctx context.Context, body AddCustomWebMCPToolsJSONRequestBody, reqEditors ...RequestEditorFn) (*AddCustomWebMCPToolsResponse, error) { + rsp, err := c.AddCustomWebMCPTools(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseReplaceCustomWebMCPToolsResponse(rsp) + return ParseAddCustomWebMCPToolsResponse(rsp) +} + +// RemoveCustomWebMCPToolWithResponse request returning *RemoveCustomWebMCPToolResponse +func (c *ClientWithResponses) RemoveCustomWebMCPToolWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RemoveCustomWebMCPToolResponse, error) { + rsp, err := c.RemoveCustomWebMCPTool(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseRemoveCustomWebMCPToolResponse(rsp) } // InvokeWebMCPToolWithBodyWithResponse request with arbitrary body returning *InvokeWebMCPToolResponse @@ -16233,8 +16360,8 @@ func (c *ClientWithResponses) InvokeWebMCPToolWithResponse(ctx context.Context, } // GetWebMCPToolsWithResponse request returning *GetWebMCPToolsResponse -func (c *ClientWithResponses) GetWebMCPToolsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetWebMCPToolsResponse, error) { - rsp, err := c.GetWebMCPTools(ctx, reqEditors...) +func (c *ClientWithResponses) GetWebMCPToolsWithResponse(ctx context.Context, params *GetWebMCPToolsParams, reqEditors ...RequestEditorFn) (*GetWebMCPToolsResponse, error) { + rsp, err := c.GetWebMCPTools(ctx, params, reqEditors...) if err != nil { return nil, err } @@ -18453,22 +18580,22 @@ func ParseStreamTelemetryEventsResponse(rsp *http.Response) (*StreamTelemetryEve return response, nil } -// ParseGetCustomWebMCPToolsResponse parses an HTTP response from a GetCustomWebMCPToolsWithResponse call -func ParseGetCustomWebMCPToolsResponse(rsp *http.Response) (*GetCustomWebMCPToolsResponse, error) { +// ParseListCustomWebMCPToolsResponse parses an HTTP response from a ListCustomWebMCPToolsWithResponse call +func ParseListCustomWebMCPToolsResponse(rsp *http.Response) (*ListCustomWebMCPToolsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCustomWebMCPToolsResponse{ + response := &ListCustomWebMCPToolsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CustomWebMCPRegistry + var dest CustomWebMCPToolsResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -18486,26 +18613,26 @@ func ParseGetCustomWebMCPToolsResponse(rsp *http.Response) (*GetCustomWebMCPTool return response, nil } -// ParseReplaceCustomWebMCPToolsResponse parses an HTTP response from a ReplaceCustomWebMCPToolsWithResponse call -func ParseReplaceCustomWebMCPToolsResponse(rsp *http.Response) (*ReplaceCustomWebMCPToolsResponse, error) { +// ParseAddCustomWebMCPToolsResponse parses an HTTP response from a AddCustomWebMCPToolsWithResponse call +func ParseAddCustomWebMCPToolsResponse(rsp *http.Response) (*AddCustomWebMCPToolsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ReplaceCustomWebMCPToolsResponse{ + response := &AddCustomWebMCPToolsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest CustomWebMCPRegistry + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CustomWebMCPToolsResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError @@ -18514,6 +18641,46 @@ func ParseReplaceCustomWebMCPToolsResponse(rsp *http.Response) (*ReplaceCustomWe } response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest ConflictError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseRemoveCustomWebMCPToolResponse parses an HTTP response from a RemoveCustomWebMCPToolWithResponse call +func ParseRemoveCustomWebMCPToolResponse(rsp *http.Response) (*RemoveCustomWebMCPToolResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RemoveCustomWebMCPToolResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -18796,18 +18963,21 @@ type ServerInterface interface { // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) StreamTelemetryEvents(w http.ResponseWriter, r *http.Request, params StreamTelemetryEventsParams) - // Get the Browser REPL custom WebMCP registry + // List custom WebMCP tools // (GET /webmcp/custom-tools) - GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) - // Replace the Browser REPL custom WebMCP registry - // (PUT /webmcp/custom-tools) - ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) + ListCustomWebMCPTools(w http.ResponseWriter, r *http.Request) + // Add custom WebMCP tools + // (POST /webmcp/custom-tools) + AddCustomWebMCPTools(w http.ResponseWriter, r *http.Request) + // Remove a custom WebMCP tool + // (DELETE /webmcp/custom-tools/{id}) + RemoveCustomWebMCPTool(w http.ResponseWriter, r *http.Request, id string) // Invoke a discovered WebMCP tool // (POST /webmcp/invoke) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) // Discover WebMCP tools across the browser // (GET /webmcp/tools) - GetWebMCPTools(w http.ResponseWriter, r *http.Request) + GetWebMCPTools(w http.ResponseWriter, r *http.Request, params GetWebMCPToolsParams) } // Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. @@ -19162,15 +19332,21 @@ func (_ Unimplemented) StreamTelemetryEvents(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusNotImplemented) } -// Get the Browser REPL custom WebMCP registry +// List custom WebMCP tools // (GET /webmcp/custom-tools) -func (_ Unimplemented) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) ListCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Add custom WebMCP tools +// (POST /webmcp/custom-tools) +func (_ Unimplemented) AddCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Replace the Browser REPL custom WebMCP registry -// (PUT /webmcp/custom-tools) -func (_ Unimplemented) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { +// Remove a custom WebMCP tool +// (DELETE /webmcp/custom-tools/{id}) +func (_ Unimplemented) RemoveCustomWebMCPTool(w http.ResponseWriter, r *http.Request, id string) { w.WriteHeader(http.StatusNotImplemented) } @@ -19182,7 +19358,7 @@ func (_ Unimplemented) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request) // Discover WebMCP tools across the browser // (GET /webmcp/tools) -func (_ Unimplemented) GetWebMCPTools(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) GetWebMCPTools(w http.ResponseWriter, r *http.Request, params GetWebMCPToolsParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -20324,11 +20500,11 @@ func (siw *ServerInterfaceWrapper) StreamTelemetryEvents(w http.ResponseWriter, handler.ServeHTTP(w, r) } -// GetCustomWebMCPTools operation middleware -func (siw *ServerInterfaceWrapper) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { +// ListCustomWebMCPTools operation middleware +func (siw *ServerInterfaceWrapper) ListCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetCustomWebMCPTools(w, r) + siw.Handler.ListCustomWebMCPTools(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -20338,11 +20514,36 @@ func (siw *ServerInterfaceWrapper) GetCustomWebMCPTools(w http.ResponseWriter, r handler.ServeHTTP(w, r) } -// ReplaceCustomWebMCPTools operation middleware -func (siw *ServerInterfaceWrapper) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { +// AddCustomWebMCPTools operation middleware +func (siw *ServerInterfaceWrapper) AddCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ReplaceCustomWebMCPTools(w, r) + siw.Handler.AddCustomWebMCPTools(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RemoveCustomWebMCPTool operation middleware +func (siw *ServerInterfaceWrapper) RemoveCustomWebMCPTool(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RemoveCustomWebMCPTool(w, r, id) })) for _, middleware := range siw.HandlerMiddlewares { @@ -20369,8 +20570,21 @@ func (siw *ServerInterfaceWrapper) InvokeWebMCPTool(w http.ResponseWriter, r *ht // GetWebMCPTools operation middleware func (siw *ServerInterfaceWrapper) GetWebMCPTools(w http.ResponseWriter, r *http.Request) { + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetWebMCPToolsParams + + // ------------- Optional query parameter "exclude_custom" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "exclude_custom", r.URL.Query(), ¶ms.ExcludeCustom, runtime.BindQueryParameterOptions{Type: "boolean", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "exclude_custom", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetWebMCPTools(w, r) + siw.Handler.GetWebMCPTools(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -20668,10 +20882,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/telemetry/stream", wrapper.StreamTelemetryEvents) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/webmcp/custom-tools", wrapper.GetCustomWebMCPTools) + r.Get(options.BaseURL+"/webmcp/custom-tools", wrapper.ListCustomWebMCPTools) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/webmcp/custom-tools", wrapper.AddCustomWebMCPTools) }) r.Group(func(r chi.Router) { - r.Put(options.BaseURL+"/webmcp/custom-tools", wrapper.ReplaceCustomWebMCPTools) + r.Delete(options.BaseURL+"/webmcp/custom-tools/{id}", wrapper.RemoveCustomWebMCPTool) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/webmcp/invoke", wrapper.InvokeWebMCPTool) @@ -23125,60 +23342,103 @@ func (response StreamTelemetryEvents200TexteventStreamResponse) VisitStreamTelem } } -type GetCustomWebMCPToolsRequestObject struct { +type ListCustomWebMCPToolsRequestObject struct { } -type GetCustomWebMCPToolsResponseObject interface { - VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error +type ListCustomWebMCPToolsResponseObject interface { + VisitListCustomWebMCPToolsResponse(w http.ResponseWriter) error } -type GetCustomWebMCPTools200JSONResponse CustomWebMCPRegistry +type ListCustomWebMCPTools200JSONResponse CustomWebMCPToolsResponse -func (response GetCustomWebMCPTools200JSONResponse) VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error { +func (response ListCustomWebMCPTools200JSONResponse) VisitListCustomWebMCPToolsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type GetCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } +type ListCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } -func (response GetCustomWebMCPTools500JSONResponse) VisitGetCustomWebMCPToolsResponse(w http.ResponseWriter) error { +func (response ListCustomWebMCPTools500JSONResponse) VisitListCustomWebMCPToolsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type ReplaceCustomWebMCPToolsRequestObject struct { - Body *ReplaceCustomWebMCPToolsJSONRequestBody +type AddCustomWebMCPToolsRequestObject struct { + Body *AddCustomWebMCPToolsJSONRequestBody } -type ReplaceCustomWebMCPToolsResponseObject interface { - VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error +type AddCustomWebMCPToolsResponseObject interface { + VisitAddCustomWebMCPToolsResponse(w http.ResponseWriter) error } -type ReplaceCustomWebMCPTools200JSONResponse CustomWebMCPRegistry +type AddCustomWebMCPTools201JSONResponse CustomWebMCPToolsResponse -func (response ReplaceCustomWebMCPTools200JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { +func (response AddCustomWebMCPTools201JSONResponse) VisitAddCustomWebMCPToolsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(201) return json.NewEncoder(w).Encode(response) } -type ReplaceCustomWebMCPTools400JSONResponse struct{ BadRequestErrorJSONResponse } +type AddCustomWebMCPTools400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response ReplaceCustomWebMCPTools400JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { +func (response AddCustomWebMCPTools400JSONResponse) VisitAddCustomWebMCPToolsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type ReplaceCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } +type AddCustomWebMCPTools409JSONResponse struct{ ConflictErrorJSONResponse } + +func (response AddCustomWebMCPTools409JSONResponse) VisitAddCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type AddCustomWebMCPTools500JSONResponse struct{ InternalErrorJSONResponse } + +func (response AddCustomWebMCPTools500JSONResponse) VisitAddCustomWebMCPToolsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type RemoveCustomWebMCPToolRequestObject struct { + Id string `json:"id"` +} + +type RemoveCustomWebMCPToolResponseObject interface { + VisitRemoveCustomWebMCPToolResponse(w http.ResponseWriter) error +} + +type RemoveCustomWebMCPTool204Response struct { +} + +func (response RemoveCustomWebMCPTool204Response) VisitRemoveCustomWebMCPToolResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type RemoveCustomWebMCPTool404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response RemoveCustomWebMCPTool404JSONResponse) VisitRemoveCustomWebMCPToolResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type RemoveCustomWebMCPTool500JSONResponse struct{ InternalErrorJSONResponse } -func (response ReplaceCustomWebMCPTools500JSONResponse) VisitReplaceCustomWebMCPToolsResponse(w http.ResponseWriter) error { +func (response RemoveCustomWebMCPTool500JSONResponse) VisitRemoveCustomWebMCPToolResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) @@ -23239,6 +23499,7 @@ func (response InvokeWebMCPTool504JSONResponse) VisitInvokeWebMCPToolResponse(w } type GetWebMCPToolsRequestObject struct { + Params GetWebMCPToolsParams } type GetWebMCPToolsResponseObject interface { @@ -23448,12 +23709,15 @@ type StrictServerInterface interface { // Stream telemetry events as Server-Sent Events // (GET /telemetry/stream) StreamTelemetryEvents(ctx context.Context, request StreamTelemetryEventsRequestObject) (StreamTelemetryEventsResponseObject, error) - // Get the Browser REPL custom WebMCP registry + // List custom WebMCP tools // (GET /webmcp/custom-tools) - GetCustomWebMCPTools(ctx context.Context, request GetCustomWebMCPToolsRequestObject) (GetCustomWebMCPToolsResponseObject, error) - // Replace the Browser REPL custom WebMCP registry - // (PUT /webmcp/custom-tools) - ReplaceCustomWebMCPTools(ctx context.Context, request ReplaceCustomWebMCPToolsRequestObject) (ReplaceCustomWebMCPToolsResponseObject, error) + ListCustomWebMCPTools(ctx context.Context, request ListCustomWebMCPToolsRequestObject) (ListCustomWebMCPToolsResponseObject, error) + // Add custom WebMCP tools + // (POST /webmcp/custom-tools) + AddCustomWebMCPTools(ctx context.Context, request AddCustomWebMCPToolsRequestObject) (AddCustomWebMCPToolsResponseObject, error) + // Remove a custom WebMCP tool + // (DELETE /webmcp/custom-tools/{id}) + RemoveCustomWebMCPTool(ctx context.Context, request RemoveCustomWebMCPToolRequestObject) (RemoveCustomWebMCPToolResponseObject, error) // Invoke a discovered WebMCP tool // (POST /webmcp/invoke) InvokeWebMCPTool(ctx context.Context, request InvokeWebMCPToolRequestObject) (InvokeWebMCPToolResponseObject, error) @@ -25207,23 +25471,23 @@ func (sh *strictHandler) StreamTelemetryEvents(w http.ResponseWriter, r *http.Re } } -// GetCustomWebMCPTools operation middleware -func (sh *strictHandler) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { - var request GetCustomWebMCPToolsRequestObject +// ListCustomWebMCPTools operation middleware +func (sh *strictHandler) ListCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + var request ListCustomWebMCPToolsRequestObject handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.GetCustomWebMCPTools(ctx, request.(GetCustomWebMCPToolsRequestObject)) + return sh.ssi.ListCustomWebMCPTools(ctx, request.(ListCustomWebMCPToolsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "GetCustomWebMCPTools") + handler = middleware(handler, "ListCustomWebMCPTools") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(GetCustomWebMCPToolsResponseObject); ok { - if err := validResponse.VisitGetCustomWebMCPToolsResponse(w); err != nil { + } else if validResponse, ok := response.(ListCustomWebMCPToolsResponseObject); ok { + if err := validResponse.VisitListCustomWebMCPToolsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -25231,11 +25495,11 @@ func (sh *strictHandler) GetCustomWebMCPTools(w http.ResponseWriter, r *http.Req } } -// ReplaceCustomWebMCPTools operation middleware -func (sh *strictHandler) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { - var request ReplaceCustomWebMCPToolsRequestObject +// AddCustomWebMCPTools operation middleware +func (sh *strictHandler) AddCustomWebMCPTools(w http.ResponseWriter, r *http.Request) { + var request AddCustomWebMCPToolsRequestObject - var body ReplaceCustomWebMCPToolsJSONRequestBody + var body AddCustomWebMCPToolsJSONRequestBody if err := json.NewDecoder(r.Body).Decode(&body); err != nil { sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return @@ -25243,18 +25507,44 @@ func (sh *strictHandler) ReplaceCustomWebMCPTools(w http.ResponseWriter, r *http request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.ReplaceCustomWebMCPTools(ctx, request.(ReplaceCustomWebMCPToolsRequestObject)) + return sh.ssi.AddCustomWebMCPTools(ctx, request.(AddCustomWebMCPToolsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "ReplaceCustomWebMCPTools") + handler = middleware(handler, "AddCustomWebMCPTools") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(ReplaceCustomWebMCPToolsResponseObject); ok { - if err := validResponse.VisitReplaceCustomWebMCPToolsResponse(w); err != nil { + } else if validResponse, ok := response.(AddCustomWebMCPToolsResponseObject); ok { + if err := validResponse.VisitAddCustomWebMCPToolsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RemoveCustomWebMCPTool operation middleware +func (sh *strictHandler) RemoveCustomWebMCPTool(w http.ResponseWriter, r *http.Request, id string) { + var request RemoveCustomWebMCPToolRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RemoveCustomWebMCPTool(ctx, request.(RemoveCustomWebMCPToolRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RemoveCustomWebMCPTool") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RemoveCustomWebMCPToolResponseObject); ok { + if err := validResponse.VisitRemoveCustomWebMCPToolResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -25294,9 +25584,11 @@ func (sh *strictHandler) InvokeWebMCPTool(w http.ResponseWriter, r *http.Request } // GetWebMCPTools operation middleware -func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request) { +func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, params GetWebMCPToolsParams) { var request GetWebMCPToolsRequestObject + request.Params = params + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { return sh.ssi.GetWebMCPTools(ctx, request.(GetWebMCPToolsRequestObject)) } @@ -25320,659 +25612,661 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request) // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "bKMDAA==", + "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", + "AAAA///aubKlsaUDAA==", } // 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 5ff9586b..8fc2031d 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "sort" + "strings" "sync" "time" @@ -16,6 +17,8 @@ import ( ) const ( + customToolNamePrefix = "custom." + customToolIDLength = len("ct_") + 16 settleDelay = 200 * time.Millisecond settleLimit = 2 * time.Second maxToolsPerSession = 256 @@ -201,6 +204,26 @@ func (c *connection) handleProtocolEvent(message cdpclient.Message) { } } +func customToolIdentity(name string) (string, string) { + if !strings.HasPrefix(name, customToolNamePrefix) { + return "", name + } + remainder := strings.TrimPrefix(name, customToolNamePrefix) + if len(remainder) <= customToolIDLength || remainder[customToolIDLength] != '.' { + return "", name + } + id := remainder[:customToolIDLength] + if !strings.HasPrefix(id, "ct_") { + return "", name + } + for _, char := range id[len("ct_"):] { + if !strings.ContainsRune("0123456789abcdef", char) { + return "", name + } + } + return id, remainder[customToolIDLength+1:] +} + func (c *connection) addTools(sessionID string, tools []toolEvent) { if !c.surface.SessionExists(sessionID) { return @@ -228,15 +251,18 @@ func (c *connection) addTools(sessionID string, tools []toolEvent) { c.toolRefs[key] = ref tracked++ } + customID, name := customToolIdentity(tool.Name) c.tools[ref] = ®isteredTool{ - ref: ref, - sessionID: sessionID, - name: tool.Name, - description: tool.Description, - inputSchema: tool.InputSchema, - annotations: tool.Annotations, - frameID: tool.FrameID, - declarative: tool.BackendNodeID != nil, + ref: ref, + sessionID: sessionID, + name: name, + registeredName: tool.Name, + description: tool.Description, + inputSchema: tool.InputSchema, + annotations: tool.Annotations, + customID: customID, + frameID: tool.FrameID, + declarative: tool.BackendNodeID != nil, } } c.signalStateChanged() @@ -266,6 +292,7 @@ func (c *connection) toolsSnapshot() []Tool { Description: tool.description, InputSchema: tool.inputSchema, Annotations: tool.annotations, + CustomID: tool.customID, Source: source, }) } @@ -326,7 +353,7 @@ func (c *connection) invoke(ctx context.Context, toolRef string, input map[strin c.stateMu.RUnlock() return InvocationResult{}, ErrToolNotFound } - sessionID, frameID, name := tool.sessionID, tool.frameID, tool.name + sessionID, frameID, name := tool.sessionID, tool.frameID, tool.registeredName awaitingSubmission := tool.declarative && (tool.annotations == nil || !tool.annotations.Autosubmit) c.stateMu.RUnlock() if !c.sessionExists(sessionID) { diff --git a/server/lib/webmcpclient/custom_tool_name_test.go b/server/lib/webmcpclient/custom_tool_name_test.go new file mode 100644 index 00000000..2cee94f1 --- /dev/null +++ b/server/lib/webmcpclient/custom_tool_name_test.go @@ -0,0 +1,17 @@ +package webmcpclient + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCustomToolIdentity(t *testing.T) { + id, name := customToolIdentity("custom.ct_0123456789abcdef.fill_payment_form") + require.Equal(t, "ct_0123456789abcdef", id) + require.Equal(t, "fill_payment_form", name) + + id, name = customToolIdentity("custom.ct_not-a-custom-id.search") + require.Empty(t, id) + require.Equal(t, "custom.ct_not-a-custom-id.search", name) +} diff --git a/server/lib/webmcpclient/types.go b/server/lib/webmcpclient/types.go index 8e39476a..848b0087 100644 --- a/server/lib/webmcpclient/types.go +++ b/server/lib/webmcpclient/types.go @@ -16,6 +16,7 @@ type Tool struct { Description string InputSchema map[string]any Annotations *Annotations + CustomID string Source ToolSource } @@ -47,14 +48,16 @@ type InvocationResult struct { } type registeredTool struct { - ref string - sessionID string - name string - description string - inputSchema map[string]any - annotations *Annotations - frameID string - declarative bool + ref string + sessionID string + name string + registeredName string + description string + inputSchema map[string]any + annotations *Annotations + customID string + frameID string + declarative bool } type toolEvent struct { diff --git a/server/openapi.yaml b/server/openapi.yaml index 9425c3a3..18eca6fe 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1360,19 +1360,28 @@ paths: get: summary: Discover WebMCP tools across the browser description: | - Returns a snapshot of native WebMCP tools available across the browser. The snapshot includes - tools registered by every open tab and by embedded content within those tabs. + Returns a snapshot of WebMCP tools available across the browser, including custom tools unless + exclude_custom is true. The snapshot includes tools registered by every open tab and by embedded + content within those tabs. On the first request, Kernel starts monitoring the browser. Tabs and windows that are already open are included, and tabs, windows, and embedded content opened later are tracked automatically. Tools are removed when the tab or embedded frame that registered them closes or navigates away. - Each tool includes an opaque tool_ref that identifies its exact live registration, along with - information about the window, tab, and optional embedded frame that provided it. Use that tool_ref - to invoke the tool. Callers do not need to manage browser debugging connections or subscribe to - browser events. + Each tool includes an opaque tool_ref that identifies its exact live registration, MCP-compatible + tool metadata, and information about the window, tab, and optional embedded frame that provided it. + Custom tools also identify their generated ID and namespace under source.custom. Use tool_ref to + invoke that exact registration. operationId: getWebMCPTools x-telemetry-category: control + parameters: + - name: exclude_custom + in: query + required: false + schema: + type: boolean + default: false + description: Exclude custom tools when true. responses: "200": description: Current WebMCP tool snapshot. @@ -1424,47 +1433,67 @@ paths: /webmcp/custom-tools: get: - summary: Get the Browser REPL custom WebMCP registry - description: | - Returns custom tool definitions held by the current Browser REPL process, their installation - status across open tabs, and the repl_id that owns the state. A reset, timeout, crash, OOM, or - API restart replaces the REPL and clears this registry. - operationId: getCustomWebMCPTools + summary: List custom WebMCP tools + description: Returns every registered custom tool with its generated ID, namespace, matcher, and MCP tool metadata. + operationId: listCustomWebMCPTools x-telemetry-category: control responses: "200": - description: Current REPL-local custom WebMCP registry. + description: Current custom WebMCP tools. content: application/json: schema: - $ref: "#/components/schemas/CustomWebMCPRegistry" + $ref: "#/components/schemas/CustomWebMCPToolsResponse" "500": $ref: "#/components/responses/InternalError" - put: - summary: Replace the Browser REPL custom WebMCP registry + post: + summary: Add custom WebMCP tools description: | - Evaluates JavaScript source in the existing persistent Browser REPL. The source registers - page-backed or CDP-backed definitions with customTools.register(...). Definitions are staged - and replace the live registry only after the source evaluates successfully. Matchers apply to - every top-level document, nested frame, and OOPIF, and matching tools are registered on the - corresponding topmost document through native imperative WebMCP. - operationId: replaceCustomWebMCPTools + Atomically adds a namespaced batch of custom tools. The JavaScript source must evaluate to a + non-empty array of definitions. Each definition supplies page-backed or CDP-backed execution, + URL matchers, MCP tool metadata including an output schema, and an execute function. Matchers + apply to top-level documents, nested frames, and OOPIFs; each matching tool is exposed once on + the corresponding topmost document and appears in the normal WebMCP tool list. + operationId: addCustomWebMCPTools x-telemetry-category: control requestBody: required: true content: application/json: schema: - $ref: "#/components/schemas/CustomWebMCPRegistryRequest" + $ref: "#/components/schemas/AddCustomWebMCPToolsRequest" responses: - "200": - description: Replaced REPL-local custom WebMCP registry. + "201": + description: Custom tools added. The response contains only the newly added tools. content: application/json: schema: - $ref: "#/components/schemas/CustomWebMCPRegistry" + $ref: "#/components/schemas/CustomWebMCPToolsResponse" "400": $ref: "#/components/responses/BadRequestError" + "409": + $ref: "#/components/responses/ConflictError" + "500": + $ref: "#/components/responses/InternalError" + + /webmcp/custom-tools/{id}: + delete: + summary: Remove a custom WebMCP tool + description: Removes one custom tool by generated ID. An invocation already in progress is not canceled. + operationId: removeCustomWebMCPTool + x-telemetry-category: control + parameters: + - name: id + in: path + required: true + schema: + type: string + pattern: "^ct_[a-f0-9]{16}$" + responses: + "204": + description: Custom tool removed. + "404": + $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" @@ -1520,13 +1549,12 @@ paths: - REPL: `repl.help`, `repl.write`, `repl.emitImage` - Browser control: `cdp`, `drainEvents`, `waitForEvent`, `gotoUrl`, `pageInfo`, `accessibilitySnapshot`, `click`, `typeText`, `fillInput`, `pressKey`, `scroll`, `captureScreenshot`, `listTabs`, `currentTab`, `switchTab`, `newTab`, `closeTab`, `ensureRealTab`, `iframeTarget`, `waitMs`, `waitForLoad`, `waitForElement`, `waitForNetworkIdle`, `js`, `uploadFile`, `httpGet` - - WebMCP: `webmcp.listTools`, `webmcp.invokeTool` - - Custom WebMCP: `customTools.register`, `customTools.remove`, `customTools.list`, `customTools.get` + - WebMCP: `webmcp.listTools`, `webmcp.addCustomTools`, `webmcp.listCustomTools`, `webmcp.removeCustomTool`, `webmcp.invokeTool` Browser-control methods are available both directly and on `browser`. The same frozen - browser-wide WebMCP client is available as `webmcp` and `browser.webmcp`. The `customTools` - registry global is shared with `GET|PUT /webmcp/custom-tools`. Common workflows: + browser-wide WebMCP client is available as `webmcp` and `browser.webmcp`, including custom + tool add, list, and remove methods. Common workflows: ```js await gotoUrl("https://example.com"); @@ -6379,15 +6407,20 @@ components: additionalProperties: false WebMCPToolAnnotations: type: object - description: Page-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + description: Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. additionalProperties: false - required: [read_only, untrusted_content, consequential, autosubmit] properties: - read_only: + readOnlyHint: + type: boolean + destructiveHint: + type: boolean + idempotentHint: type: boolean - untrusted_content: + openWorldHint: type: boolean - consequential: + consequentialHint: + type: boolean + untrustedContentHint: type: boolean autosubmit: type: boolean @@ -6403,6 +6436,16 @@ components: url: type: string description: Current frame URL with the fragment omitted. + WebMCPCustomToolSource: + type: object + additionalProperties: false + required: [id, namespace] + properties: + id: + type: string + pattern: "^ct_[a-f0-9]{16}$" + namespace: + type: string WebMCPToolSource: type: object additionalProperties: false @@ -6427,23 +6470,37 @@ components: - $ref: "#/components/schemas/WebMCPToolFrame" nullable: true description: Embedded frame that registered the tool, or null when the top-level page registered it. - WebMCPTool: + custom: + $ref: "#/components/schemas/WebMCPCustomToolSource" + WebMCPToolMetadata: type: object additionalProperties: false - required: [tool_ref, name, description, input_schema, source] + required: [name, description, inputSchema] properties: - tool_ref: - type: string - description: Opaque reference for invoking this exact live registration. It becomes invalid when its document or browser process is replaced. name: type: string + title: + type: string description: type: string - input_schema: + inputSchema: + type: object + additionalProperties: true + outputSchema: type: object additionalProperties: true annotations: $ref: "#/components/schemas/WebMCPToolAnnotations" + WebMCPTool: + type: object + additionalProperties: false + required: [tool_ref, tool, source] + properties: + tool_ref: + type: string + description: Opaque reference for invoking this exact live registration. It becomes invalid when its document or browser process is replaced. + tool: + $ref: "#/components/schemas/WebMCPToolMetadata" source: $ref: "#/components/schemas/WebMCPToolSource" WebMCPToolsResponse: @@ -6455,14 +6512,17 @@ components: type: array items: $ref: "#/components/schemas/WebMCPTool" - CustomWebMCPRegistryRequest: + AddCustomWebMCPToolsRequest: type: object - required: [source] + required: [namespace, source] properties: + namespace: + type: string + pattern: "^[A-Za-z0-9_.-]{1,128}$" source: type: string maxLength: 8000000 - description: JavaScript evaluated in the existing Browser REPL with customTools and all Browser REPL globals available. + description: JavaScript expression that evaluates to a non-empty array of custom tool definitions. additionalProperties: false CustomWebMCPMatch: type: object @@ -6474,63 +6534,36 @@ components: items: type: string additionalProperties: false + CustomWebMCPToolMetadata: + allOf: + - $ref: "#/components/schemas/WebMCPToolMetadata" + - type: object + required: [outputSchema] CustomWebMCPDefinition: type: object - required: [id, kind, match, tool, revision] + required: [id, namespace, kind, match, tool] properties: id: type: string + pattern: "^ct_[a-f0-9]{16}$" + namespace: + type: string kind: type: string pattern: "^(page|cdp)$" match: $ref: "#/components/schemas/CustomWebMCPMatch" tool: - type: object - description: MCP-compatible tool metadata as submitted to customTools.register(). - additionalProperties: true - output_schema: - type: object - additionalProperties: true - revision: - type: integer + $ref: "#/components/schemas/CustomWebMCPToolMetadata" additionalProperties: false - CustomWebMCPInstallation: + CustomWebMCPToolsResponse: type: object - required: [target_id, definition_ids, errors] - properties: - target_id: - type: string - definition_ids: - type: array - items: - type: string - errors: - type: array - items: - type: string - additionalProperties: false - CustomWebMCPRegistry: - type: object - required: [repl_id, revision, source, source_dirty, tools, installations] + required: [tools] properties: - repl_id: - type: string - revision: - type: integer - source: - type: string - source_dirty: - type: boolean - description: True when customTools.register() or customTools.remove() changed the live registry after the last PUT. tools: type: array items: $ref: "#/components/schemas/CustomWebMCPDefinition" - installations: - type: array - items: - $ref: "#/components/schemas/CustomWebMCPInstallation" additionalProperties: false WebMCPInvokeRequest: type: object diff --git a/server/runtime/browser-repl-help.ts b/server/runtime/browser-repl-help.ts index 134fe691..69af4757 100644 --- a/server/runtime/browser-repl-help.ts +++ b/server/runtime/browser-repl-help.ts @@ -7,7 +7,7 @@ export interface BrowserReplHelpEntry { example?: string; } -export type BrowserReplHelpGroup = 'repl' | 'browser' | 'webmcp' | 'customTools'; +export type BrowserReplHelpGroup = 'repl' | 'browser' | 'webmcp'; export const browserReplHelpRegistry = { repl: { @@ -173,30 +173,24 @@ export const browserReplHelpRegistry = { 'Fetch a URL from the VM and return its response body as text. Supports custom headers and `timeoutSec` (default `20`); non-2xx responses throw. Its timeout covers body consumption and is clamped below the active execution deadline.', }, }, - customTools: { - register: { - signature: 'customTools.register(definition)', + webmcp: { + listTools: { + signature: 'webmcp.listTools(options?)', description: - 'Add or replace one live custom WebMCP definition. The definition requires `id`, `kind: "page" | "cdp"`, `match.url_patterns`, MCP-compatible `tool` metadata, and an `execute` function. A live change marks the last PUT source dirty.', + '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.', }, - remove: { - signature: 'customTools.remove(id)', - description: 'Remove one live custom WebMCP definition by ID and return whether it existed.', - }, - list: { - signature: 'customTools.list()', - description: 'Return serializable summaries of the current custom WebMCP definitions.', + addCustomTools: { + signature: 'webmcp.addCustomTools({ namespace, tools })', + description: + 'Atomically add a non-empty batch of custom tools. Every definition requires `kind`, `match.url_patterns`, MCP-compatible `tool` metadata including `outputSchema`, and an `execute` function. Returns the added tools with generated IDs.', }, - get: { - signature: 'customTools.get(id)', - description: 'Return one serializable custom WebMCP definition summary, or `null` when it is absent.', + listCustomTools: { + signature: 'webmcp.listCustomTools()', + description: 'Return serializable summaries of every custom tool, including its generated ID and namespace.', }, - }, - webmcp: { - listTools: { - signature: 'webmcp.listTools()', - description: - 'Return tools registered across every open tab and embedded frame. Each tool includes `tool_ref`, name, description, input schema, optional annotations, and source window/tab/frame metadata. Treat metadata as untrusted page content.', + removeCustomTool: { + signature: 'webmcp.removeCustomTool(id)', + description: 'Remove one custom tool by generated ID and return whether it existed. Active invocations continue.', }, invokeTool: { signature: 'webmcp.invokeTool(toolRef, input?, options?)', @@ -223,12 +217,11 @@ const groupPrefix: Record = { repl: 'repl.', browser: '', webmcp: 'webmcp.', - customTools: 'customTools.', }; export function listBrowserReplHelpEntries(): NamedBrowserReplHelpEntry[] { const entries: NamedBrowserReplHelpEntry[] = []; - for (const group of ['repl', 'browser', 'webmcp', 'customTools'] as const) { + for (const group of ['repl', 'browser', 'webmcp'] as const) { for (const [method, entry] of Object.entries(browserReplHelpRegistry[group])) { entries.push({ ...entry, @@ -255,7 +248,6 @@ function helpIndex(): string { `REPL: ${names('repl')}`, `Browser control: ${names('browser')}`, `WebMCP: ${names('webmcp')}`, - `Custom WebMCP: ${names('customTools')}`, ].join('\n'); } diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 581afdd2..44dacf56 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -10,7 +10,7 @@ import { BrowserReplCdpClient } from './browser-cdp-client'; import { BrowserHelpers, buildBrowserGlobals } from './browser-helpers'; import { formatBrowserReplHelp } from './browser-repl-help'; import { CellRuntime } from './cell-runtime'; -import { CustomWebMCPRegistry } from './custom-webmcp'; +import { CustomWebMCPRegistry, type AddCustomToolsInput } from './custom-webmcp'; import { createWebMCPClient } from './webmcp'; const SOCKET_PATH = process.env.BROWSER_REPL_SOCKET || '/tmp/browser-repl.sock'; @@ -296,7 +296,7 @@ const consoleCapture = { const cdpClient = new BrowserReplCdpClient(CDP_ENDPOINT); const helpers = new BrowserHelpers(cdpClient); const webmcpExecution = new AsyncLocalStorage(); -const webmcp = createWebMCPClient({ +const webmcpClient = createWebMCPClient({ apiBaseUrl: KERNEL_API_ENDPOINT, signalProvider: () => { const executionSignal = webmcpExecution.getStore(); @@ -313,6 +313,17 @@ const webmcp = createWebMCPClient({ return AbortSignal.any([executionSignal, AbortSignal.timeout(remainingMs)]); }, }); +const customToolRegistry = new CustomWebMCPRegistry( + cdpClient, + (signal, callback) => webmcpExecution.run(signal, callback), +); +customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); +const webmcp = Object.freeze({ + ...webmcpClient, + addCustomTools: customToolRegistry.add, + listCustomTools: customToolRegistry.list, + removeCustomTool: customToolRegistry.remove, +}); const browserGlobals = buildBrowserGlobals(helpers); const browserNamespace = Object.freeze({ ...(browserGlobals.browser as Record), @@ -377,27 +388,26 @@ async function evaluate(code: string): Promise { await cellRuntime.evaluate(code); } -const customToolRegistry = new CustomWebMCPRegistry( - cdpClient, - REPL_ID, - (signal, callback) => webmcpExecution.run(signal, callback), -); -customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); -contextGlobal.customTools = Object.freeze({ - register: customToolRegistry.register, - remove: customToolRegistry.remove, - list: customToolRegistry.list, - get: customToolRegistry.get, -}); +async function evaluateValue(source: string): Promise { + const key = `__kernelCustomTools_${crypto.randomUUID().replaceAll('-', '')}`; + try { + await evaluate(`globalThis[${JSON.stringify(key)}] = await (${source})`); + return contextGlobal[key]; + } finally { + delete contextGlobal[key]; + } +} // Request handling -type ExecuteOperation = 'execute' | 'custom_tools_get' | 'custom_tools_replace'; +type ExecuteOperation = 'execute' | 'custom_tools_list' | 'custom_tools_add' | 'custom_tool_remove'; interface ExecuteRequest { id: string; code: string; operation?: ExecuteOperation; + namespace?: string; + custom_tool_id?: string; timeout_ms?: number; } @@ -406,12 +416,14 @@ interface ExecuteResponse { repl_id: string; success: boolean; error?: string; + error_code?: string; stack?: string; content: ContentItem[]; content_truncated: boolean; timed_out?: boolean; exiting?: boolean; result?: unknown; + custom_tools?: ReturnType; duration_ms: number; } @@ -461,10 +473,17 @@ async function executeRequest( case 'execute': await evaluate(request.code); return undefined; - case 'custom_tools_get': + case 'custom_tools_list': return customToolRegistry.current(); - case 'custom_tools_replace': - return customToolRegistry.replace(request.code, evaluate); + case 'custom_tools_add': { + const tools = await evaluateValue(request.code); + return customToolRegistry.add({ + namespace: request.namespace ?? '', + tools: tools as AddCustomToolsInput['tools'], + }); + } + case 'custom_tool_remove': + return customToolRegistry.remove(request.custom_tool_id ?? ''); default: throw new Error(`unknown Browser REPL operation: ${request.operation}`); } @@ -477,6 +496,7 @@ async function executeRequest( content: collector.items, content_truncated: collector.truncated, result, + custom_tools: customToolRegistry.list(), duration_ms: Date.now() - start, }; } catch (err: any) { @@ -485,6 +505,7 @@ async function executeRequest( repl_id: REPL_ID, success: false, error: boundedProtocolText(err?.message ?? err, MAX_ERROR_BYTES), + error_code: typeof err?.code === 'string' ? boundedProtocolText(err.code, 128) : undefined, stack: typeof err?.stack === 'string' ? boundedProtocolText(err.stack, MAX_STACK_BYTES) : undefined, content: collector.items, content_truncated: collector.truncated, @@ -493,6 +514,7 @@ async function executeRequest( // destructively, per the spec's timeout semantics) before serving // another execution. timed_out: timedOut || undefined, + custom_tools: customToolRegistry.list(), duration_ms: Date.now() - start, }; } finally { @@ -709,6 +731,7 @@ function onUncaughtException(err: unknown): void { content: inFlight.collector.items, content_truncated: inFlight.collector.truncated, exiting: true, + custom_tools: customToolRegistry.list(), duration_ms: Date.now() - inFlight.start, }); } catch { diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index 2cef7565..9ad6e4f9 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -11,6 +11,8 @@ const RECONCILE_EVENT_DELAY_MS = 50; const RECONCILE_COMMAND_TIMEOUT_MS = 5_000; const MAX_OUTPUT_BYTES = 1 << 20; const MAX_ERROR_BYTES = 64 << 10; +const CUSTOM_TOOL_NAME_PREFIX = 'custom.'; +const CUSTOM_TOOL_NAMESPACE_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/; type JsonSchema = Record; type Validator = ReturnType; @@ -26,15 +28,14 @@ interface CustomToolMetadata { title?: string; description: string; inputSchema: JsonSchema; + outputSchema: JsonSchema; annotations?: Record; } interface CustomToolDefinitionInput { - id: string; kind: ToolKind; match: CustomToolMatch; tool: CustomToolMetadata; - outputSchema?: JsonSchema; execute: ( input: Record, context: CustomToolExecutionContext, @@ -42,9 +43,12 @@ interface CustomToolDefinitionInput { } interface CustomToolDefinition extends CustomToolDefinitionInput { + id: string; + namespace: string; + registeredName: string; revision: number; inputValidator: Validator; - outputValidator?: Validator; + outputValidator: Validator; pageExecuteSource?: string; } @@ -106,24 +110,17 @@ interface ActiveInvocation { controller: AbortController; } -export interface CustomToolsSnapshot { - repl_id: string; - revision: number; - source: string; - source_dirty: boolean; - tools: Array<{ - id: string; - kind: ToolKind; - match: CustomToolMatch; - tool: CustomToolMetadata; - outputSchema?: JsonSchema; - revision: number; - }>; - installations: Array<{ - target_id: string; - definition_ids: string[]; - errors: string[]; - }>; +export interface CustomToolSummary { + id: string; + namespace: string; + kind: ToolKind; + match: CustomToolMatch; + tool: CustomToolMetadata; +} + +export interface AddCustomToolsInput { + namespace: string; + tools: CustomToolDefinitionInput[]; } function clone(value: T): T { @@ -134,6 +131,27 @@ function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } +function newCustomToolID(): string { + return `ct_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`; +} + +function registeredToolName(id: string, name: string): string { + const registeredName = `${CUSTOM_TOOL_NAME_PREFIX}${id}.${name}`; + if (registeredName.length > 128) { + throw new Error(`tool.name is too long after custom namespacing: ${name}`); + } + return registeredName; +} + +class CustomToolConflictError extends Error { + readonly code = 'custom_tool_conflict'; + + constructor(message: string) { + super(message); + this.name = 'CustomToolConflictError'; + } +} + function errorMessage(error: unknown): string { const message = error instanceof Error ? error.message : String(error); if (Buffer.byteLength(message) <= MAX_ERROR_BYTES) return message; @@ -225,25 +243,20 @@ function flattenFrames(frame: FrameInfo): FrameInfo[] { return frames; } -function definitionPublic(definition: CustomToolDefinition) { - const result: CustomToolsSnapshot['tools'][number] = { +function definitionPublic(definition: CustomToolDefinition): CustomToolSummary { + return { id: definition.id, + namespace: definition.namespace, kind: definition.kind, match: clone(definition.match), tool: clone(definition.tool), - revision: definition.revision, }; - if (definition.outputSchema) result.outputSchema = clone(definition.outputSchema); - return result; } export class CustomWebMCPRegistry { private definitions = new Map(); - private source = ''; - private sourceDirty = false; private revision = 0; private emptyRegistryCleaned = false; - private staging: Map | null = null; private pages = new Map(); private sessions = new Map(); private iframeSessions = new Map(); @@ -259,16 +272,13 @@ export class CustomWebMCPRegistry { private readonly unsubscribeDisconnect: () => void; private onError?: (message: string) => void; private readonly client: BrowserReplCdpClient; - private readonly replID: string; private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; constructor( client: BrowserReplCdpClient, - replID: string, runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, ) { this.client = client; - this.replID = replID; this.runInvocation = runInvocation; this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); @@ -279,94 +289,68 @@ export class CustomWebMCPRegistry { this.onError = handler; } - register = (input: CustomToolDefinitionInput): ReturnType => { - const destination = this.staging ?? this.definitions; - const prior = destination.get(input.id) ?? (this.staging ? this.definitions.get(input.id) : undefined); - const definition = this.validateDefinition(input, prior?.revision ?? 0); - for (const existing of destination.values()) { - if (existing.id !== definition.id && existing.tool.name === definition.tool.name) { - throw new Error(`tool name already registered: ${definition.tool.name}`); - } + add = async (input: AddCustomToolsInput): Promise => { + if (!input || typeof input !== 'object') throw new Error('custom tool batch must be an object'); + if (typeof input.namespace !== 'string' || !CUSTOM_TOOL_NAMESPACE_PATTERN.test(input.namespace)) { + throw new Error('namespace must be 1-128 ASCII letters, numbers, dots, underscores, or hyphens'); } - destination.set(definition.id, definition); - if (!this.staging) { - this.revision++; - this.sourceDirty = true; - this.scheduleReconcile(); + if (!Array.isArray(input.tools) || input.tools.length === 0) { + throw new Error('tools must be a non-empty array'); } - return definitionPublic(definition); - }; - remove = (id: string): boolean => { - const destination = this.staging ?? this.definitions; - const removed = destination.delete(id); - if (removed && !this.staging) { - this.revision++; - this.sourceDirty = true; - if (this.definitions.size === 0) this.emptyRegistryCleaned = false; - this.scheduleReconcile(); + const occupied = new Set( + [...this.definitions.values()].map((definition) => `${definition.namespace}\u0000${definition.tool.name}`), + ); + const additions: CustomToolDefinition[] = []; + for (const tool of input.tools) { + const name = isRecord(tool) && isRecord(tool.tool) && typeof tool.tool.name === 'string' + ? tool.tool.name + : ''; + const key = `${input.namespace}\u0000${name}`; + if (occupied.has(key)) { + throw new CustomToolConflictError(`custom tool already exists: ${input.namespace}/${name}`); + } + occupied.add(key); + let id: string; + do id = newCustomToolID(); while (this.definitions.has(id) || additions.some((item) => item.id === id)); + additions.push(this.validateDefinition(tool, id, input.namespace, this.revision + 1)); } - return removed; + + for (const definition of additions) this.definitions.set(definition.id, definition); + this.revision++; + this.scheduleReconcile(); + await this.settleReconciliation(); + return additions.map(definitionPublic); + }; + + remove = async (id: string): Promise => { + const removed = this.definitions.delete(id); + if (!removed) return false; + this.revision++; + if (this.definitions.size === 0) this.emptyRegistryCleaned = false; + this.scheduleReconcile(); + await this.settleReconciliation(); + return true; }; - list = (): CustomToolsSnapshot['tools'] => { + list = (): CustomToolSummary[] => { return [...this.definitions.values()] .map(definitionPublic) .sort((a, b) => a.id.localeCompare(b.id)); }; - get = (id: string): CustomToolsSnapshot['tools'][number] | null => { - const definition = this.definitions.get(id); - return definition ? definitionPublic(definition) : null; + current = async (): Promise => { + await this.settleReconciliation(); + return this.list(); }; - async replace(source: string, evaluate: (source: string) => Promise): Promise { - if (this.staging) throw new Error('custom tool replacement is already in progress'); - this.staging = new Map(); - try { - await evaluate(`await (async () => {\n${source}\n})()`); - this.definitions = this.staging; - this.source = source; - this.sourceDirty = false; - this.revision++; - if (this.definitions.size === 0) this.emptyRegistryCleaned = false; - } finally { - this.staging = null; - } + private async settleReconciliation(): Promise { try { await this.reconcile(); } catch (error) { this.reportError(error); this.retryReconcile(); } - return this.snapshot(); - } - - async current(): Promise { - try { - await this.reconcile(); - } catch (error) { - this.reportError(error); - this.retryReconcile(); - } - return this.snapshot(); - } - - snapshot(): CustomToolsSnapshot { - return { - repl_id: this.replID, - revision: this.revision, - source: this.source, - source_dirty: this.sourceDirty, - tools: this.list(), - installations: [...this.pages.values()] - .map((page) => ({ - target_id: page.targetId, - definition_ids: [...page.matches.keys()].sort(), - errors: [...page.errors], - })) - .sort((a, b) => a.target_id.localeCompare(b.target_id)), - }; } async dispose(): Promise { @@ -395,11 +379,13 @@ export class CustomWebMCPRegistry { this.iframeTargetsBySession.clear(); } - private validateDefinition(input: CustomToolDefinitionInput, previousRevision: number): CustomToolDefinition { + private validateDefinition( + input: CustomToolDefinitionInput, + id: string, + namespace: string, + revision: number, + ): CustomToolDefinition { if (!input || typeof input !== 'object') throw new Error('definition must be an object'); - if (typeof input.id !== 'string' || input.id.trim() === '') { - throw new Error('definition.id must be a non-empty string'); - } if (input.kind !== 'page' && input.kind !== 'cdp') { throw new Error('definition.kind must be page or cdp'); } @@ -409,15 +395,16 @@ export class CustomWebMCPRegistry { for (const pattern of input.match.url_patterns) parseURLPattern(pattern); if (typeof input.execute !== 'function') throw new Error('definition.execute must be a function'); - const toolResult = ToolSchema.safeParse({ ...input.tool, outputSchema: input.outputSchema }); + const toolResult = ToolSchema.safeParse(input.tool); if (!toolResult.success) { throw new Error(`invalid MCP tool definition: ${formatToolSchemaError(toolResult.error)}`); } + if (!isRecord(input.tool.outputSchema)) { + throw new Error('tool.outputSchema is required and must be an object'); + } const inputValidator = compileSchema(input.tool.inputSchema, 'tool.inputSchema'); - const outputValidator = input.outputSchema - ? compileSchema(input.outputSchema, 'outputSchema') - : undefined; + const outputValidator = compileSchema(input.tool.outputSchema, 'tool.outputSchema'); const pageExecuteSource = input.kind === 'page' ? input.execute.toString() : undefined; if (pageExecuteSource) { try { @@ -428,13 +415,14 @@ export class CustomWebMCPRegistry { } return { - id: input.id, + id, + namespace, + registeredName: registeredToolName(id, input.tool.name), kind: input.kind, match: clone(input.match), tool: clone(input.tool), - outputSchema: input.outputSchema ? clone(input.outputSchema) : undefined, execute: input.execute, - revision: previousRevision + 1, + revision, inputValidator, outputValidator, pageExecuteSource, @@ -782,7 +770,7 @@ export class CustomWebMCPRegistry { const metadata = JSON.stringify({ id: definition.id, kind: definition.kind, - tool: definition.tool, + tool: {...definition.tool, name: definition.registeredName}, revision: definition.revision, }); if (definition.kind === 'cdp') return metadata; diff --git a/server/runtime/webmcp.test.ts b/server/runtime/webmcp.test.ts index 11abae14..88c0c5ad 100644 --- a/server/runtime/webmcp.test.ts +++ b/server/runtime/webmcp.test.ts @@ -15,9 +15,11 @@ test('lists browser-wide tools through the image API', async () => { const requests: Array<{url: string; init?: RequestInit}> = []; const tool = { tool_ref: 'wmcp_test', - name: 'search', - description: 'Search', - input_schema: {type: 'object'}, + tool: { + name: 'search', + description: 'Search', + inputSchema: {type: 'object'}, + }, source: { window_id: 1, tab_id: 2, @@ -37,7 +39,9 @@ test('lists browser-wide tools through the image API', async () => { assert.equal(Object.isFrozen(client), true); assert.deepEqual(await client.listTools(), [tool]); + assert.deepEqual(await client.listTools({excludeCustom: true}), [tool]); assert.equal(requests[0].url, 'http://127.0.0.1:10001/webmcp/tools'); + assert.equal(requests[1].url, 'http://127.0.0.1:10001/webmcp/tools?exclude_custom=true'); assert.equal(requests[0].init?.signal, controller.signal); }); diff --git a/server/runtime/webmcp.ts b/server/runtime/webmcp.ts index 5d040593..acdde331 100644 --- a/server/runtime/webmcp.ts +++ b/server/runtime/webmcp.ts @@ -3,20 +3,32 @@ export interface WebMCPToolFrame { url: string; } +export interface WebMCPCustomToolSource { + id: string; + namespace: string; +} + export interface WebMCPToolSource { window_id: number; tab_id: number; page_title: string; page_url: string; frame: WebMCPToolFrame | null; + custom?: WebMCPCustomToolSource; } -export interface WebMCPTool { - tool_ref: string; +export interface WebMCPToolMetadata { name: string; + title?: string; description: string; - input_schema: Record; + inputSchema: Record; + outputSchema?: Record; annotations?: Record; +} + +export interface WebMCPTool { + tool_ref: string; + tool: WebMCPToolMetadata; source: WebMCPToolSource; } @@ -31,8 +43,12 @@ export interface WebMCPInvokeOptions { timeoutSec?: number; } +export interface WebMCPListOptions { + excludeCustom?: boolean; +} + export interface WebMCPClient { - listTools(): Promise; + listTools(options?: WebMCPListOptions): Promise; invokeTool( toolRef: string, input?: Record, @@ -109,8 +125,9 @@ export function createWebMCPClient({ }; const client: WebMCPClient = { - async listTools() { - const body = await request('/webmcp/tools'); + async listTools(options = {}) { + const query = options.excludeCustom ? '?exclude_custom=true' : ''; + const body = await request(`/webmcp/tools${query}`); if (!isRecord(body) || !Array.isArray(body.tools)) { throw new Error('WebMCP tools response is invalid'); } diff --git a/server/scripts/replhelp/main.mjs b/server/scripts/replhelp/main.mjs index 7a4911bf..26800792 100644 --- a/server/scripts/replhelp/main.mjs +++ b/server/scripts/replhelp/main.mjs @@ -24,7 +24,6 @@ function markdownReference() { repl: 'REPL methods', browser: 'Browser-control methods', webmcp: 'WebMCP methods', - customTools: 'Custom WebMCP methods', }; return Object.keys(titles) .map((group) => { @@ -46,7 +45,6 @@ function openApiMethodList() { ` - REPL: ${names('repl', 'repl.')}`, ` - Browser control: ${names('browser', '')}`, ` - WebMCP: ${names('webmcp', 'webmcp.')}`, - ` - Custom WebMCP: ${names('customTools', 'customTools.')}`, ].join('\n'); } From 5f28c8d1946f07e41c4f14878a0d95d70e5672b1 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:31:32 +0000 Subject: [PATCH 07/18] Publish custom tool metadata during registration --- server/cmd/api/api/browser_repl.go | 22 +++++++++----- server/cmd/api/api/custom_webmcp_test.go | 38 ++++++++++++++++++++++++ server/runtime/browser-repl.ts | 14 +++++---- server/runtime/custom-webmcp.ts | 20 +++++++++++-- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 2b379a44..a18a1ddd 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -105,6 +105,12 @@ func (m *browserReplManager) setCustomTools(tools []oapi.CustomWebMCPDefinition) } func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPDefinition { + if data, err := os.ReadFile(browserReplCustomToolsPath()); err == nil { + var tools []oapi.CustomWebMCPDefinition + if json.Unmarshal(data, &tools) == nil { + m.setCustomTools(tools) + } + } m.customToolsMu.RLock() defer m.customToolsMu.RUnlock() tools := make(map[string]oapi.CustomWebMCPDefinition, len(m.customTools)) @@ -114,6 +120,10 @@ func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPD return tools } +func browserReplCustomToolsPath() string { + return browserReplSocketPath() + ".custom-tools.json" +} + // browserReplSocketPath returns the Unix socket path for the REPL daemon. // Overridable for tests. func browserReplSocketPath() string { @@ -230,6 +240,7 @@ func (m *browserReplManager) clearLocked(ctx context.Context, child *browserRepl if m.child == child { m.child = nil m.setCustomTools(nil) + _ = os.Remove(browserReplCustomToolsPath()) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) } @@ -399,10 +410,9 @@ type browserReplDaemonResponse struct { // exception details and is exiting non-zero. The API treats it like a // timeout — terminate the handle and report repl_terminated — so the // state loss is explicit to the caller. - Exiting bool `json:"exiting,omitempty"` - Result json.RawMessage `json:"result,omitempty"` - CustomTools *[]oapi.CustomWebMCPDefinition `json:"custom_tools,omitempty"` - DurationMs int `json:"duration_ms"` + Exiting bool `json:"exiting,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + DurationMs int `json:"duration_ms"` } // browserReplRequest is the already-encoded request sent over the daemon @@ -534,10 +544,6 @@ func (m *browserReplManager) executeLocked(ctx context.Context, request *browser if resp.ReplID != child.id { return nil, fmt.Errorf("response repl_id mismatch: expected %s, got %s", child.id, resp.ReplID) } - if resp.CustomTools != nil { - m.setCustomTools(*resp.CustomTools) - } - return &resp, nil } diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 57c8e2a7..683fd5d7 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -2,7 +2,9 @@ package api import ( "context" + "fmt" "testing" + "time" "github.com/kernel/kernel-images/server/lib/oapi" "github.com/stretchr/testify/require" @@ -86,6 +88,42 @@ func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { require.Empty(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools) } +func TestCustomWebMCPMetadataIsVisibleBeforeReplCellCompletes(t *testing.T) { + svc := newBrowserReplSvc(t) + code := fmt.Sprintf(` + await webmcp.addCustomTools({namespace: "midcell.example", tools: %s}); + await waitMs(1500); + `, customWebMCPTestSource) + done := make(chan error, 1) + go func() { + response, err := svc.ExecuteBrowserRepl(context.Background(), oapi.ExecuteBrowserReplRequestObject{ + Body: &oapi.ExecuteBrowserReplJSONRequestBody{Code: code}, + }) + if err == nil { + result, ok := response.(oapi.ExecuteBrowserRepl200JSONResponse) + if !ok || !result.Success { + err = fmt.Errorf("unexpected response: %#v", response) + } + } + done <- err + }() + + require.Eventually(t, func() bool { + for _, tool := range svc.browserRepl.customToolsSnapshot() { + if tool.Namespace == "midcell.example" { + return true + } + } + return false + }, 5*time.Second, 10*time.Millisecond) + select { + case err := <-done: + require.FailNow(t, "REPL cell completed before metadata became visible", "%v", err) + default: + } + require.NoError(t, <-done) +} + func TestCustomWebMCPAllowsSameNameAcrossNamespaces(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 44dacf56..e5bec2f7 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -3,7 +3,7 @@ import { AsyncLocalStorage } from 'async_hooks'; import { createServer, Socket } from 'net'; import { StringDecoder } from 'string_decoder'; -import { unlinkSync, existsSync, promises as fsp } from 'fs'; +import { unlinkSync, existsSync, renameSync, writeFileSync, promises as fsp } from 'fs'; import vm from 'vm'; import util from 'util'; import { BrowserReplCdpClient } from './browser-cdp-client'; @@ -16,6 +16,7 @@ import { createWebMCPClient } from './webmcp'; const SOCKET_PATH = process.env.BROWSER_REPL_SOCKET || '/tmp/browser-repl.sock'; const REPL_ID = process.env.BROWSER_REPL_ID || 'unknown'; const CDP_ENDPOINT = process.env.CDP_ENDPOINT || 'ws://127.0.0.1:9222'; +const CUSTOM_TOOLS_STATE_PATH = `${SOCKET_PATH}.custom-tools.json`; // Keep the endpoint discoverable by dynamically imported browser clients even // when the image relies on the runtime's default rather than an explicit env. process.env.CDP_ENDPOINT = CDP_ENDPOINT; @@ -313,10 +314,17 @@ const webmcpClient = createWebMCPClient({ return AbortSignal.any([executionSignal, AbortSignal.timeout(remainingMs)]); }, }); +const publishCustomTools = (tools: ReturnType) => { + const temporaryPath = `${CUSTOM_TOOLS_STATE_PATH}.${process.pid}.tmp`; + writeFileSync(temporaryPath, safeStringify(tools), {mode: 0o600}); + renameSync(temporaryPath, CUSTOM_TOOLS_STATE_PATH); +}; const customToolRegistry = new CustomWebMCPRegistry( cdpClient, (signal, callback) => webmcpExecution.run(signal, callback), + publishCustomTools, ); +publishCustomTools([]); customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); const webmcp = Object.freeze({ ...webmcpClient, @@ -423,7 +431,6 @@ interface ExecuteResponse { timed_out?: boolean; exiting?: boolean; result?: unknown; - custom_tools?: ReturnType; duration_ms: number; } @@ -496,7 +503,6 @@ async function executeRequest( content: collector.items, content_truncated: collector.truncated, result, - custom_tools: customToolRegistry.list(), duration_ms: Date.now() - start, }; } catch (err: any) { @@ -514,7 +520,6 @@ async function executeRequest( // destructively, per the spec's timeout semantics) before serving // another execution. timed_out: timedOut || undefined, - custom_tools: customToolRegistry.list(), duration_ms: Date.now() - start, }; } finally { @@ -731,7 +736,6 @@ function onUncaughtException(err: unknown): void { content: inFlight.collector.items, content_truncated: inFlight.collector.truncated, exiting: true, - custom_tools: customToolRegistry.list(), duration_ms: Date.now() - inFlight.start, }); } catch { diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index 9ad6e4f9..b180ba47 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -273,13 +273,16 @@ export class CustomWebMCPRegistry { private onError?: (message: string) => void; private readonly client: BrowserReplCdpClient; private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; + private readonly publishDefinitions: (tools: CustomToolSummary[]) => void; constructor( client: BrowserReplCdpClient, runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, + publishDefinitions: (tools: CustomToolSummary[]) => void, ) { this.client = client; this.runInvocation = runInvocation; + this.publishDefinitions = publishDefinitions; this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); this.scheduleReconcile(); @@ -317,6 +320,12 @@ export class CustomWebMCPRegistry { } for (const definition of additions) this.definitions.set(definition.id, definition); + try { + this.publishDefinitions(this.list()); + } catch (error) { + for (const definition of additions) this.definitions.delete(definition.id); + throw error; + } this.revision++; this.scheduleReconcile(); await this.settleReconciliation(); @@ -324,8 +333,15 @@ export class CustomWebMCPRegistry { }; remove = async (id: string): Promise => { - const removed = this.definitions.delete(id); - if (!removed) return false; + const definition = this.definitions.get(id); + if (!definition) return false; + this.definitions.delete(id); + try { + this.publishDefinitions(this.list()); + } catch (error) { + this.definitions.set(id, definition); + throw error; + } this.revision++; if (this.definitions.size === 0) this.emptyRegistryCleaned = false; this.scheduleReconcile(); From cd5715003da9e99cfd02385eef369aa7b25c66f5 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:37:46 +0000 Subject: [PATCH 08/18] Keep custom discovery metadata current --- server/cmd/api/api/browser_repl.go | 35 ++- server/cmd/api/api/custom_webmcp_test.go | 19 ++ server/cmd/api/api/webmcp.go | 11 + server/cmd/api/api/webmcp_test.go | 5 +- server/lib/oapi/oapi.go | 377 ++++++++++++----------- server/openapi.yaml | 35 ++- server/runtime/browser-repl.ts | 2 +- 7 files changed, 292 insertions(+), 192 deletions(-) diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index a18a1ddd..152f505a 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -75,12 +75,13 @@ type browserReplChild struct { // browserReplManager owns execution admission and the persistent Node child. // Lifecycle synchronization stays behind its Execute and Shutdown methods. type browserReplManager struct { - admission chan struct{} - lifecycle context.Context - stop context.CancelCauseFunc - child *browserReplChild // guarded by admission - customToolsMu sync.RWMutex - customTools map[string]oapi.CustomWebMCPDefinition + admission chan struct{} + lifecycle context.Context + stop context.CancelCauseFunc + child *browserReplChild // guarded by admission + customToolsMu sync.RWMutex + customToolsReplID string + customTools map[string]oapi.CustomWebMCPDefinition } func newBrowserReplManager() *browserReplManager { @@ -95,9 +96,10 @@ func newBrowserReplManager() *browserReplManager { } } -func (m *browserReplManager) setCustomTools(tools []oapi.CustomWebMCPDefinition) { +func (m *browserReplManager) setCustomTools(replID string, tools []oapi.CustomWebMCPDefinition) { m.customToolsMu.Lock() defer m.customToolsMu.Unlock() + m.customToolsReplID = replID m.customTools = make(map[string]oapi.CustomWebMCPDefinition, len(tools)) for _, tool := range tools { m.customTools[tool.Id] = tool @@ -105,10 +107,18 @@ func (m *browserReplManager) setCustomTools(tools []oapi.CustomWebMCPDefinition) } func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPDefinition { - if data, err := os.ReadFile(browserReplCustomToolsPath()); err == nil { - var tools []oapi.CustomWebMCPDefinition - if json.Unmarshal(data, &tools) == nil { - m.setCustomTools(tools) + m.customToolsMu.RLock() + replID := m.customToolsReplID + m.customToolsMu.RUnlock() + if replID != "" { + if data, err := os.ReadFile(browserReplCustomToolsPath()); err == nil { + var state struct { + ReplID string `json:"repl_id"` + Tools []oapi.CustomWebMCPDefinition `json:"tools"` + } + if json.Unmarshal(data, &state) == nil && state.ReplID == replID { + m.setCustomTools(replID, state.Tools) + } } } m.customToolsMu.RLock() @@ -239,7 +249,7 @@ func closedWaitChannel(err error) chan error { func (m *browserReplManager) clearLocked(ctx context.Context, child *browserReplChild) { if m.child == child { m.child = nil - m.setCustomTools(nil) + m.setCustomTools("", nil) _ = os.Remove(browserReplCustomToolsPath()) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) @@ -282,6 +292,7 @@ func (m *browserReplManager) startLocked(ctx context.Context) error { child.done <- cmd.Wait() }() m.child = child + m.setCustomTools(replID, nil) deadline := time.Now().Add(browserReplStartupTimeout) for { diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 683fd5d7..f6a3b4a2 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -3,6 +3,7 @@ package api import ( "context" "fmt" + "os" "testing" "time" @@ -88,6 +89,24 @@ func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { require.Empty(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools) } +func TestCustomWebMCPSnapshotRejectsDifferentRepl(t *testing.T) { + svc := newBrowserReplSvc(t) + ctx := context.Background() + added, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{Namespace: "example.com", Source: customWebMCPTestSource}, + }) + require.NoError(t, err) + tool := added.(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] + require.Contains(t, svc.browserRepl.customToolsSnapshot(), tool.Id) + + require.NoError(t, os.WriteFile( + browserReplCustomToolsPath(), + []byte(`{"repl_id":"different-repl","tools":[]}`), + 0o600, + )) + require.Contains(t, svc.browserRepl.customToolsSnapshot(), tool.Id) +} + func TestCustomWebMCPMetadataIsVisibleBeforeReplCellCompletes(t *testing.T) { svc := newBrowserReplSvc(t) code := fmt.Sprintf(` diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 4e9f354e..3d2292c9 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -92,6 +92,17 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Annotations: definition.Tool.Annotations, } } + responseTool.Name = responseTool.Tool.Name + responseTool.Description = responseTool.Tool.Description + responseTool.InputSchema = responseTool.Tool.InputSchema + if tool.Annotations != nil { + responseTool.Annotations = &oapi.WebMCPToolLegacyAnnotations{ + ReadOnly: tool.Annotations.ReadOnly, + UntrustedContent: tool.Annotations.UntrustedContent, + Consequential: tool.Annotations.Consequential, + Autosubmit: tool.Annotations.Autosubmit, + } + } responseTools = append(responseTools, responseTool) } return oapi.GetWebMCPTools200JSONResponse{Tools: responseTools}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 953fd139..052f3f8d 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -63,12 +63,13 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { require.Equal(t, "https://payments.example/element", tool.Source.Frame.Url) require.Empty(t, tool.Tool.InputSchema) require.True(t, *tool.Tool.Annotations.ConsequentialHint) + require.True(t, tool.Annotations.Consequential) } func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { outputSchema := map[string]any{"type": "object"} manager := newBrowserReplManager() - manager.setCustomTools([]oapi.CustomWebMCPDefinition{{ + manager.setCustomTools("test-repl", []oapi.CustomWebMCPDefinition{{ Id: "ct_0123456789abcdef", Namespace: "stripe.com", Kind: "cdp", @@ -111,7 +112,7 @@ func TestGetWebMCPToolsSerializesNullFrameForTopLevelTool(t *testing.T) { require.NoError(t, err) payload, err := json.Marshal(response.(oapi.GetWebMCPTools200JSONResponse)) require.NoError(t, err) - require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","tool":{"name":"search","description":"","inputSchema":{}},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) + require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","name":"search","description":"","input_schema":{},"tool":{"name":"search","description":"","inputSchema":{}},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) } func TestInvokeWebMCPToolReturnsPageResult(t *testing.T) { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index e26d3911..6656c68b 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6941,6 +6941,21 @@ type WebMCPInvokeRequest struct { // WebMCPTool defines model for WebMCPTool. type WebMCPTool struct { + // Annotations Deprecated compatibility copy of tool annotations. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Annotations *WebMCPToolLegacyAnnotations `json:"annotations,omitempty"` + + // Description Deprecated compatibility copy of tool.description. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Description string `json:"description"` + + // InputSchema Deprecated compatibility copy of tool.inputSchema. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + InputSchema map[string]interface{} `json:"input_schema"` + + // Name Deprecated compatibility copy of tool.name. + // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set + Name string `json:"name"` Source WebMCPToolSource `json:"source"` Tool WebMCPToolMetadata `json:"tool"` @@ -6968,6 +6983,14 @@ type WebMCPToolFrame struct { Url string `json:"url"` } +// WebMCPToolLegacyAnnotations Deprecated compatibility copy of WebMCP annotations. +type WebMCPToolLegacyAnnotations struct { + Autosubmit bool `json:"autosubmit"` + Consequential bool `json:"consequential"` + ReadOnly bool `json:"read_only"` + UntrustedContent bool `json:"untrusted_content"` +} + // WebMCPToolMetadata defines model for WebMCPToolMetadata. type WebMCPToolMetadata struct { // Annotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. @@ -26091,182 +26114,184 @@ var swaggerSpec = []string{ "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", - "AAAA///aubKlsaUDAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 18eca6fe..0ff97f20 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -6405,6 +6405,21 @@ components: Wall-clock time at which the current configuration was applied. Omitted when telemetry is not configured. additionalProperties: false + WebMCPToolLegacyAnnotations: + type: object + description: Deprecated compatibility copy of WebMCP annotations. + deprecated: true + additionalProperties: false + required: [read_only, untrusted_content, consequential, autosubmit] + properties: + read_only: + type: boolean + untrusted_content: + type: boolean + consequential: + type: boolean + autosubmit: + type: boolean WebMCPToolAnnotations: type: object description: Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. @@ -6494,7 +6509,7 @@ components: WebMCPTool: type: object additionalProperties: false - required: [tool_ref, tool, source] + required: [tool_ref, tool, source, name, description, input_schema] properties: tool_ref: type: string @@ -6503,6 +6518,24 @@ components: $ref: "#/components/schemas/WebMCPToolMetadata" source: $ref: "#/components/schemas/WebMCPToolSource" + name: + type: string + deprecated: true + description: Deprecated compatibility copy of tool.name. + description: + type: string + deprecated: true + description: Deprecated compatibility copy of tool.description. + input_schema: + type: object + deprecated: true + description: Deprecated compatibility copy of tool.inputSchema. + additionalProperties: true + annotations: + allOf: + - $ref: "#/components/schemas/WebMCPToolLegacyAnnotations" + deprecated: true + description: Deprecated compatibility copy of tool annotations. WebMCPToolsResponse: type: object additionalProperties: false diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index e5bec2f7..1b69488e 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -316,7 +316,7 @@ const webmcpClient = createWebMCPClient({ }); const publishCustomTools = (tools: ReturnType) => { const temporaryPath = `${CUSTOM_TOOLS_STATE_PATH}.${process.pid}.tmp`; - writeFileSync(temporaryPath, safeStringify(tools), {mode: 0o600}); + writeFileSync(temporaryPath, safeStringify({repl_id: REPL_ID, tools}), {mode: 0o600}); renameSync(temporaryPath, CUSTOM_TOOLS_STATE_PATH); }; const customToolRegistry = new CustomWebMCPRegistry( From fa05d841fc29d4516e8cbac85dc33f06de6870de Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:43:42 +0000 Subject: [PATCH 09/18] Guard custom tool metadata generations --- server/cmd/api/api/browser_repl.go | 43 +++++++++++++++++++++--------- server/cmd/api/api/webmcp_test.go | 16 ++++++++++- server/runtime/browser-repl.ts | 6 ++--- server/runtime/custom-webmcp.ts | 8 +++--- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 152f505a..736fca46 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -75,13 +75,14 @@ type browserReplChild struct { // browserReplManager owns execution admission and the persistent Node child. // Lifecycle synchronization stays behind its Execute and Shutdown methods. type browserReplManager struct { - admission chan struct{} - lifecycle context.Context - stop context.CancelCauseFunc - child *browserReplChild // guarded by admission - customToolsMu sync.RWMutex - customToolsReplID string - customTools map[string]oapi.CustomWebMCPDefinition + admission chan struct{} + lifecycle context.Context + stop context.CancelCauseFunc + child *browserReplChild // guarded by admission + customToolsMu sync.RWMutex + customToolsReplID string + customToolsRevision int + customTools map[string]oapi.CustomWebMCPDefinition } func newBrowserReplManager() *browserReplManager { @@ -96,10 +97,25 @@ func newBrowserReplManager() *browserReplManager { } } -func (m *browserReplManager) setCustomTools(replID string, tools []oapi.CustomWebMCPDefinition) { +func (m *browserReplManager) setCustomTools(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { m.customToolsMu.Lock() defer m.customToolsMu.Unlock() m.customToolsReplID = replID + m.customToolsRevision = revision + m.replaceCustomToolsLocked(tools) +} + +func (m *browserReplManager) setCustomToolsIfCurrent(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { + m.customToolsMu.Lock() + defer m.customToolsMu.Unlock() + if m.customToolsReplID != replID || revision < m.customToolsRevision { + return + } + m.customToolsRevision = revision + m.replaceCustomToolsLocked(tools) +} + +func (m *browserReplManager) replaceCustomToolsLocked(tools []oapi.CustomWebMCPDefinition) { m.customTools = make(map[string]oapi.CustomWebMCPDefinition, len(tools)) for _, tool := range tools { m.customTools[tool.Id] = tool @@ -113,11 +129,12 @@ func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPD if replID != "" { if data, err := os.ReadFile(browserReplCustomToolsPath()); err == nil { var state struct { - ReplID string `json:"repl_id"` - Tools []oapi.CustomWebMCPDefinition `json:"tools"` + ReplID string `json:"repl_id"` + Revision int `json:"revision"` + Tools []oapi.CustomWebMCPDefinition `json:"tools"` } if json.Unmarshal(data, &state) == nil && state.ReplID == replID { - m.setCustomTools(replID, state.Tools) + m.setCustomToolsIfCurrent(replID, state.Revision, state.Tools) } } } @@ -249,7 +266,7 @@ func closedWaitChannel(err error) chan error { func (m *browserReplManager) clearLocked(ctx context.Context, child *browserReplChild) { if m.child == child { m.child = nil - m.setCustomTools("", nil) + m.setCustomTools("", 0, nil) _ = os.Remove(browserReplCustomToolsPath()) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) @@ -292,7 +309,7 @@ func (m *browserReplManager) startLocked(ctx context.Context) error { child.done <- cmd.Wait() }() m.child = child - m.setCustomTools(replID, nil) + m.setCustomTools(replID, 0, nil) deadline := time.Now().Add(browserReplStartupTimeout) for { diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 052f3f8d..04180d69 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -66,10 +66,24 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { require.True(t, tool.Annotations.Consequential) } +func TestCustomToolCacheRejectsStaleGenerationAndRevision(t *testing.T) { + manager := newBrowserReplManager() + current := oapi.CustomWebMCPDefinition{Id: "ct_0123456789abcdef", Namespace: "current"} + stale := oapi.CustomWebMCPDefinition{Id: "ct_fedcba9876543210", Namespace: "stale"} + manager.setCustomTools("current-repl", 2, []oapi.CustomWebMCPDefinition{current}) + + manager.setCustomToolsIfCurrent("old-repl", 3, []oapi.CustomWebMCPDefinition{stale}) + manager.setCustomToolsIfCurrent("current-repl", 1, []oapi.CustomWebMCPDefinition{stale}) + + tools := manager.customToolsSnapshot() + require.Contains(t, tools, current.Id) + require.NotContains(t, tools, stale.Id) +} + func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { outputSchema := map[string]any{"type": "object"} manager := newBrowserReplManager() - manager.setCustomTools("test-repl", []oapi.CustomWebMCPDefinition{{ + manager.setCustomTools("test-repl", 1, []oapi.CustomWebMCPDefinition{{ Id: "ct_0123456789abcdef", Namespace: "stripe.com", Kind: "cdp", diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 1b69488e..cdba4fcf 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -314,9 +314,9 @@ const webmcpClient = createWebMCPClient({ return AbortSignal.any([executionSignal, AbortSignal.timeout(remainingMs)]); }, }); -const publishCustomTools = (tools: ReturnType) => { +const publishCustomTools = (tools: ReturnType, revision: number) => { const temporaryPath = `${CUSTOM_TOOLS_STATE_PATH}.${process.pid}.tmp`; - writeFileSync(temporaryPath, safeStringify({repl_id: REPL_ID, tools}), {mode: 0o600}); + writeFileSync(temporaryPath, safeStringify({repl_id: REPL_ID, revision, tools}), {mode: 0o600}); renameSync(temporaryPath, CUSTOM_TOOLS_STATE_PATH); }; const customToolRegistry = new CustomWebMCPRegistry( @@ -324,7 +324,7 @@ const customToolRegistry = new CustomWebMCPRegistry( (signal, callback) => webmcpExecution.run(signal, callback), publishCustomTools, ); -publishCustomTools([]); +publishCustomTools([], 0); customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); const webmcp = Object.freeze({ ...webmcpClient, diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index b180ba47..05cf94b0 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -273,12 +273,12 @@ export class CustomWebMCPRegistry { private onError?: (message: string) => void; private readonly client: BrowserReplCdpClient; private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; - private readonly publishDefinitions: (tools: CustomToolSummary[]) => void; + private readonly publishDefinitions: (tools: CustomToolSummary[], revision: number) => void; constructor( client: BrowserReplCdpClient, runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, - publishDefinitions: (tools: CustomToolSummary[]) => void, + publishDefinitions: (tools: CustomToolSummary[], revision: number) => void, ) { this.client = client; this.runInvocation = runInvocation; @@ -321,7 +321,7 @@ export class CustomWebMCPRegistry { for (const definition of additions) this.definitions.set(definition.id, definition); try { - this.publishDefinitions(this.list()); + this.publishDefinitions(this.list(), this.revision + 1); } catch (error) { for (const definition of additions) this.definitions.delete(definition.id); throw error; @@ -337,7 +337,7 @@ export class CustomWebMCPRegistry { if (!definition) return false; this.definitions.delete(id); try { - this.publishDefinitions(this.list()); + this.publishDefinitions(this.list(), this.revision + 1); } catch (error) { this.definitions.set(id, definition); throw error; From aadc0d49fcd8fe0657d76b5fee1914d86bc62797 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:06:24 +0000 Subject: [PATCH 10/18] Clarify custom WebMCP API documentation --- server/cmd/api/api/webmcp.go | 11 - server/cmd/api/api/webmcp_test.go | 3 +- server/lib/oapi/oapi.go | 570 +++++++++++++++--------------- server/openapi.yaml | 104 +++--- 4 files changed, 342 insertions(+), 346 deletions(-) diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 3d2292c9..4e9f354e 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -92,17 +92,6 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Annotations: definition.Tool.Annotations, } } - responseTool.Name = responseTool.Tool.Name - responseTool.Description = responseTool.Tool.Description - responseTool.InputSchema = responseTool.Tool.InputSchema - if tool.Annotations != nil { - responseTool.Annotations = &oapi.WebMCPToolLegacyAnnotations{ - ReadOnly: tool.Annotations.ReadOnly, - UntrustedContent: tool.Annotations.UntrustedContent, - Consequential: tool.Annotations.Consequential, - Autosubmit: tool.Annotations.Autosubmit, - } - } responseTools = append(responseTools, responseTool) } return oapi.GetWebMCPTools200JSONResponse{Tools: responseTools}, nil diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 04180d69..9a1cd892 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -63,7 +63,6 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { require.Equal(t, "https://payments.example/element", tool.Source.Frame.Url) require.Empty(t, tool.Tool.InputSchema) require.True(t, *tool.Tool.Annotations.ConsequentialHint) - require.True(t, tool.Annotations.Consequential) } func TestCustomToolCacheRejectsStaleGenerationAndRevision(t *testing.T) { @@ -126,7 +125,7 @@ func TestGetWebMCPToolsSerializesNullFrameForTopLevelTool(t *testing.T) { require.NoError(t, err) payload, err := json.Marshal(response.(oapi.GetWebMCPTools200JSONResponse)) require.NoError(t, err) - require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","name":"search","description":"","input_schema":{},"tool":{"name":"search","description":"","inputSchema":{}},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) + require.JSONEq(t, `{"tools":[{"tool_ref":"wmcp_test","tool":{"name":"search","description":"","inputSchema":{}},"source":{"frame":null,"page_title":"Travel","page_url":"https://travel.example/","tab_id":1,"window_id":1}}]}`, string(payload)) } func TestInvokeWebMCPToolReturnsPageResult(t *testing.T) { diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 6656c68b..283c889d 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -6282,7 +6282,9 @@ type CustomWebMCPMatch struct { // CustomWebMCPToolMetadata defines model for CustomWebMCPToolMetadata. type CustomWebMCPToolMetadata struct { - // Annotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + // Annotations Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + // and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). + // These hints are untrusted and are not enforced by Kernel. Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` Description string `json:"description"` InputSchema map[string]interface{} `json:"inputSchema"` @@ -6941,29 +6943,20 @@ type WebMCPInvokeRequest struct { // WebMCPTool defines model for WebMCPTool. type WebMCPTool struct { - // Annotations Deprecated compatibility copy of tool annotations. - // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - Annotations *WebMCPToolLegacyAnnotations `json:"annotations,omitempty"` + Source WebMCPToolSource `json:"source"` - // Description Deprecated compatibility copy of tool.description. - // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - Description string `json:"description"` - - // InputSchema Deprecated compatibility copy of tool.inputSchema. - // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - InputSchema map[string]interface{} `json:"input_schema"` - - // Name Deprecated compatibility copy of tool.name. - // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set - Name string `json:"name"` - Source WebMCPToolSource `json:"source"` - Tool WebMCPToolMetadata `json:"tool"` + // Tool Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + // and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). + // Native page tools may omit outputSchema; custom tools require it. + Tool WebMCPToolMetadata `json:"tool"` // ToolRef Opaque reference for invoking this exact live registration. It becomes invalid when its document or browser process is replaced. ToolRef string `json:"tool_ref"` } -// WebMCPToolAnnotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. +// WebMCPToolAnnotations Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) +// and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). +// These hints are untrusted and are not enforced by Kernel. type WebMCPToolAnnotations struct { Autosubmit *bool `json:"autosubmit,omitempty"` ConsequentialHint *bool `json:"consequentialHint,omitempty"` @@ -6983,17 +6976,13 @@ type WebMCPToolFrame struct { Url string `json:"url"` } -// WebMCPToolLegacyAnnotations Deprecated compatibility copy of WebMCP annotations. -type WebMCPToolLegacyAnnotations struct { - Autosubmit bool `json:"autosubmit"` - Consequential bool `json:"consequential"` - ReadOnly bool `json:"read_only"` - UntrustedContent bool `json:"untrusted_content"` -} - -// WebMCPToolMetadata defines model for WebMCPToolMetadata. +// WebMCPToolMetadata Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) +// and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). +// Native page tools may omit outputSchema; custom tools require it. type WebMCPToolMetadata struct { - // Annotations Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + // Annotations Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + // and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). + // These hints are untrusted and are not enforced by Kernel. Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` Description string `json:"description"` InputSchema map[string]interface{} `json:"inputSchema"` @@ -26033,265 +26022,272 @@ var swaggerSpec = []string{ "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", + "46wDAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 0ff97f20..a6242b03 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1368,8 +1368,8 @@ paths: open are included, and tabs, windows, and embedded content opened later are tracked automatically. Tools are removed when the tab or embedded frame that registered them closes or navigates away. - Each tool includes an opaque tool_ref that identifies its exact live registration, MCP-compatible - tool metadata, and information about the window, tab, and optional embedded frame that provided it. + Each tool includes an opaque tool_ref that identifies its exact live registration, tool metadata, + and information about the window, tab, and optional embedded frame that provided it. Custom tools also identify their generated ID and namespace under source.custom. Use tool_ref to invoke that exact registration. operationId: getWebMCPTools @@ -1449,11 +1449,13 @@ paths: post: summary: Add custom WebMCP tools description: | - Atomically adds a namespaced batch of custom tools. The JavaScript source must evaluate to a - non-empty array of definitions. Each definition supplies page-backed or CDP-backed execution, - URL matchers, MCP tool metadata including an output schema, and an execute function. Matchers - apply to top-level documents, nested frames, and OOPIFs; each matching tool is exposed once on - the corresponding topmost document and appears in the normal WebMCP tool list. + Add a namespaced batch of custom tools. A custom tool can be page-backed or CDP-backed. + Page-backed tools execute in the page via JavaScript. CDP-backed tools execute via CDP and + can use all browser REPL tools (see `/repl`). The source must evaluate to a non-empty array + of definitions with URL matchers, tool metadata (including an output schema), and execute + functions. The batch is added atomically. Matchers apply to top-level documents and nested + frames, including out-of-process iframes; each matching tool is exposed once on the tab's + top-level document and appears in `GET /webmcp/tools`. operationId: addCustomWebMCPTools x-telemetry-category: control requestBody: @@ -1462,6 +1464,41 @@ paths: application/json: schema: $ref: "#/components/schemas/AddCustomWebMCPToolsRequest" + examples: + page_backed: + summary: Read a title in the page + value: + namespace: example.com + source: | + [{ + kind: "page", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_page_title", + description: "Read the page title.", + inputSchema: {type: "object", additionalProperties: false}, + outputSchema: {type: "object", properties: {title: {type: "string"}}, required: ["title"]}, + }, + execute: async () => ({title: document.title}), + }] + cdp_backed: + summary: Read a title via CDP + value: + namespace: example.com + source: | + [{ + kind: "cdp", + match: {url_patterns: ["https://example.com/*"]}, + tool: { + name: "read_cdp_title", + description: "Read the tab title via CDP.", + inputSchema: {type: "object", additionalProperties: false}, + outputSchema: {type: "object", properties: {title: {type: "string"}}, required: ["title"]}, + }, + execute: async (_input, {matches}) => ({ + title: await js(() => document.title, {targetId: matches[0].top_target_id}), + }), + }] responses: "201": description: Custom tools added. The response contains only the newly added tools. @@ -1554,7 +1591,9 @@ paths: Browser-control methods are available both directly and on `browser`. The same frozen browser-wide WebMCP client is available as `webmcp` and `browser.webmcp`, including custom - tool add, list, and remove methods. Common workflows: + tool add, list, and remove methods. `webmcp.listTools()` discovers tools through + `GET /webmcp/tools`, and `webmcp.invokeTool()` calls `POST /webmcp/invoke`. + Common workflows: ```js await gotoUrl("https://example.com"); @@ -1570,9 +1609,8 @@ paths: Top-level bindings and dynamically imported modules persist until reset or process replacement. Expression values are ignored; emit output with `repl.write(...)`, console - methods, or `repl.emitImage(...)`. Pinned `patchright`, `playwright-core`, - `@modelcontextprotocol/core`, and `@modelcontextprotocol/server` packages are available through - dynamic `import()`, and `cdp()` provides unrestricted DevTools access. + methods, or `repl.emitImage(...)`. Pinned `patchright` and `playwright-core` packages are + available through dynamic `import()`, and `cdp()` provides unrestricted DevTools access. The runtime starts lazily and is owned by the API process. Reset, API restart, timeout, crash, OOM, or protocol failure destroys its state and changes its CUID2 `repl_id`. @@ -6405,24 +6443,12 @@ components: Wall-clock time at which the current configuration was applied. Omitted when telemetry is not configured. additionalProperties: false - WebMCPToolLegacyAnnotations: - type: object - description: Deprecated compatibility copy of WebMCP annotations. - deprecated: true - additionalProperties: false - required: [read_only, untrusted_content, consequential, autosubmit] - properties: - read_only: - type: boolean - untrusted_content: - type: boolean - consequential: - type: boolean - autosubmit: - type: boolean WebMCPToolAnnotations: type: object - description: Tool-provided behavioral hints. These values are untrusted and are not enforced by Kernel. + description: | + Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). + These hints are untrusted and are not enforced by Kernel. additionalProperties: false properties: readOnlyHint: @@ -6489,6 +6515,10 @@ components: $ref: "#/components/schemas/WebMCPCustomToolSource" WebMCPToolMetadata: type: object + description: | + Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). + Native page tools may omit outputSchema; custom tools require it. additionalProperties: false required: [name, description, inputSchema] properties: @@ -6509,7 +6539,7 @@ components: WebMCPTool: type: object additionalProperties: false - required: [tool_ref, tool, source, name, description, input_schema] + required: [tool_ref, tool, source] properties: tool_ref: type: string @@ -6518,24 +6548,6 @@ components: $ref: "#/components/schemas/WebMCPToolMetadata" source: $ref: "#/components/schemas/WebMCPToolSource" - name: - type: string - deprecated: true - description: Deprecated compatibility copy of tool.name. - description: - type: string - deprecated: true - description: Deprecated compatibility copy of tool.description. - input_schema: - type: object - deprecated: true - description: Deprecated compatibility copy of tool.inputSchema. - additionalProperties: true - annotations: - allOf: - - $ref: "#/components/schemas/WebMCPToolLegacyAnnotations" - deprecated: true - description: Deprecated compatibility copy of tool annotations. WebMCPToolsResponse: type: object additionalProperties: false From 2b0c967b9ef94db8a3eb00bdc81672654251db3e Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:40:25 +0000 Subject: [PATCH 11/18] Simplify custom WebMCP operations and namespace updates --- server/cmd/api/api/browser_repl.go | 46 +- server/cmd/api/api/custom_webmcp.go | 59 +- server/cmd/api/api/custom_webmcp_test.go | 45 +- server/cmd/api/api/webmcp.go | 2 +- server/cmd/api/api/webmcp_test.go | 43 +- server/docs/repl.md | 8 +- server/lib/oapi/oapi.go | 1364 ++++++++--------- server/lib/webmcpclient/client.go | 13 +- .../lib/webmcpclient/custom_tool_name_test.go | 16 +- server/openapi.yaml | 41 +- server/runtime/browser-repl-help.ts | 16 +- server/runtime/browser-repl.ts | 42 +- server/runtime/custom-webmcp.ts | 42 +- server/runtime/package-lock.json | 42 + server/runtime/package.json | 1 + 15 files changed, 924 insertions(+), 856 deletions(-) diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 736fca46..43d863b4 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -97,7 +97,9 @@ func newBrowserReplManager() *browserReplManager { } } -func (m *browserReplManager) setCustomTools(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { +// replaceCustomToolsSnapshot refreshes the Go-side discovery cache from the daemon's +// complete, atomically published snapshot. Adding tools never replaces the registry. +func (m *browserReplManager) replaceCustomToolsSnapshot(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { m.customToolsMu.Lock() defer m.customToolsMu.Unlock() m.customToolsReplID = replID @@ -105,7 +107,7 @@ func (m *browserReplManager) setCustomTools(replID string, revision int, tools [ m.replaceCustomToolsLocked(tools) } -func (m *browserReplManager) setCustomToolsIfCurrent(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { +func (m *browserReplManager) refreshCustomToolsSnapshot(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { m.customToolsMu.Lock() defer m.customToolsMu.Unlock() if m.customToolsReplID != replID || revision < m.customToolsRevision { @@ -134,7 +136,7 @@ func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPD Tools []oapi.CustomWebMCPDefinition `json:"tools"` } if json.Unmarshal(data, &state) == nil && state.ReplID == replID { - m.setCustomToolsIfCurrent(replID, state.Revision, state.Tools) + m.refreshCustomToolsSnapshot(replID, state.Revision, state.Tools) } } } @@ -266,7 +268,7 @@ func closedWaitChannel(err error) chan error { func (m *browserReplManager) clearLocked(ctx context.Context, child *browserReplChild) { if m.child == child { m.child = nil - m.setCustomTools("", 0, nil) + m.replaceCustomToolsSnapshot("", 0, nil) _ = os.Remove(browserReplCustomToolsPath()) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) @@ -309,7 +311,7 @@ func (m *browserReplManager) startLocked(ctx context.Context) error { child.done <- cmd.Wait() }() m.child = child - m.setCustomTools(replID, 0, nil) + m.replaceCustomToolsSnapshot(replID, 0, nil) deadline := time.Now().Add(browserReplStartupTimeout) for { @@ -411,12 +413,9 @@ func (m *browserReplManager) killLocked(ctx context.Context, reason string) { // browserReplDaemonRequest is the wire format sent to the REPL daemon. type browserReplDaemonRequest struct { - ID string `json:"id"` - Code string `json:"code"` - Operation string `json:"operation,omitempty"` - Namespace string `json:"namespace,omitempty"` - CustomToolID string `json:"custom_tool_id,omitempty"` - TimeoutMs int `json:"timeout_ms,omitempty"` + ID string `json:"id"` + Code string `json:"code"` + TimeoutMs int `json:"timeout_ms,omitempty"` } // browserReplDaemonResponse is the wire format returned by the REPL daemon. @@ -438,9 +437,8 @@ type browserReplDaemonResponse struct { // exception details and is exiting non-zero. The API treats it like a // timeout — terminate the handle and report repl_terminated — so the // state loss is explicit to the caller. - Exiting bool `json:"exiting,omitempty"` - Result json.RawMessage `json:"result,omitempty"` - DurationMs int `json:"duration_ms"` + Exiting bool `json:"exiting,omitempty"` + DurationMs int `json:"duration_ms"` } // browserReplRequest is the already-encoded request sent over the daemon @@ -455,28 +453,14 @@ type browserReplRequest struct { // disabled. The daemon's limit applies to the line without its trailing // newline, so the encoded request must fit before it is sent. func prepareBrowserReplRequest(code string, timeout time.Duration) (*browserReplRequest, error) { - return prepareBrowserReplOperation(code, "", timeout) -} - -func prepareBrowserReplOperation(code, operation string, timeout time.Duration) (*browserReplRequest, error) { - return prepareBrowserReplOperationWithCustomTools(code, operation, "", "", timeout) -} - -func prepareBrowserReplOperationWithCustomTools( - code, operation, namespace, customToolID string, - timeout time.Duration, -) (*browserReplRequest, error) { id := uuid.New().String() var buf bytes.Buffer encoder := json.NewEncoder(&buf) encoder.SetEscapeHTML(false) if err := encoder.Encode(browserReplDaemonRequest{ - ID: id, - Code: code, - Operation: operation, - Namespace: namespace, - CustomToolID: customToolID, - TimeoutMs: int(timeout.Milliseconds()), + ID: id, + Code: code, + TimeoutMs: int(timeout.Milliseconds()), }); err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index dce6d06b..9fc4e248 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -15,9 +15,6 @@ const ( customWebMCPOperationTimeout = 60 * time.Second maxCustomWebMCPSourceBytes = 8_000_000 maxCustomWebMCPRequestBytes = maxCustomWebMCPSourceBytes + (4 << 10) - customWebMCPListOperation = "custom_tools_list" - customWebMCPAddOperation = "custom_tools_add" - customWebMCPRemoveOperation = "custom_tool_remove" ) type customWebMCPExecutionError struct { @@ -28,7 +25,7 @@ type customWebMCPExecutionError struct { func (e *customWebMCPExecutionError) Error() string { return e.message } func (s *ApiService) ListCustomWebMCPTools(ctx context.Context, _ oapi.ListCustomWebMCPToolsRequestObject) (oapi.ListCustomWebMCPToolsResponseObject, error) { - tools, err := s.browserRepl.customWebMCPOperation(ctx, customWebMCPListOperation, "", "", "") + tools, err := s.browserRepl.customWebMCPTools(ctx, `repl.write(JSON.stringify(webmcp.listCustomTools()))`) if err != nil { logger.FromContext(ctx).Error("failed to list custom WebMCP tools", "err", err) return oapi.ListCustomWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to list custom WebMCP tools"}}, nil @@ -45,13 +42,10 @@ func (s *ApiService) AddCustomWebMCPTools(ctx context.Context, request oapi.AddC } RecordTelemetryCode(ctx, request.Body.Source) - tools, err := s.browserRepl.customWebMCPOperation( - ctx, - customWebMCPAddOperation, - request.Body.Source, - request.Body.Namespace, - "", - ) + namespace, _ := json.Marshal(request.Body.Namespace) + forceOverwrite := request.Body.ForceOverwriteNamespace != nil && *request.Body.ForceOverwriteNamespace + code := fmt.Sprintf(`repl.write(JSON.stringify(await webmcp.addCustomTools({namespace: %s, tools: await (%s), forceOverwriteNamespace: %t})))`, namespace, request.Body.Source, forceOverwrite) + tools, err := s.browserRepl.customWebMCPTools(ctx, code) if err != nil { var executionErr *customWebMCPExecutionError if errors.As(err, &executionErr) { @@ -79,7 +73,8 @@ func (s *ApiService) RemoveCustomWebMCPTool(ctx context.Context, request oapi.Re } func (m *browserReplManager) removeCustomWebMCPTool(ctx context.Context, id string) (bool, error) { - result, err := m.customWebMCPOperationRaw(ctx, customWebMCPRemoveOperation, "", "", id) + encodedID, _ := json.Marshal(id) + result, err := m.executeCustomWebMCPCode(ctx, fmt.Sprintf(`repl.write(JSON.stringify(await webmcp.removeCustomTool(%s)))`, encodedID)) if err != nil { return false, err } @@ -90,11 +85,8 @@ func (m *browserReplManager) removeCustomWebMCPTool(ctx context.Context, id stri return removed, nil } -func (m *browserReplManager) customWebMCPOperation( - ctx context.Context, - operation, source, namespace, customToolID string, -) ([]oapi.CustomWebMCPDefinition, error) { - result, err := m.customWebMCPOperationRaw(ctx, operation, source, namespace, customToolID) +func (m *browserReplManager) customWebMCPTools(ctx context.Context, code string) ([]oapi.CustomWebMCPDefinition, error) { + result, err := m.executeCustomWebMCPCode(ctx, code) if err != nil { return nil, err } @@ -110,10 +102,7 @@ func (m *browserReplManager) customWebMCPOperation( return tools, nil } -func (m *browserReplManager) customWebMCPOperationRaw( - ctx context.Context, - operation, source, namespace, customToolID string, -) (json.RawMessage, error) { +func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code string) (json.RawMessage, error) { if err := m.acquire(ctx); err != nil { return nil, err } @@ -129,13 +118,7 @@ func (m *browserReplManager) customWebMCPOperationRaw( } ctx = operationCtx - request, err := prepareBrowserReplOperationWithCustomTools( - source, - operation, - namespace, - customToolID, - customWebMCPOperationTimeout, - ) + request, err := prepareBrowserReplRequest(code, customWebMCPOperationTimeout) if err != nil { return nil, &customWebMCPExecutionError{message: err.Error()} } @@ -164,8 +147,22 @@ func (m *browserReplManager) customWebMCPOperationRaw( if !response.Success { return nil, &customWebMCPExecutionError{code: response.ErrorCode, message: response.Error} } - if len(response.Result) == 0 { - return nil, errors.New("Browser REPL returned no custom WebMCP result") + if response.ContentTruncated { + return nil, errors.New("Browser REPL truncated the custom WebMCP result") } - return response.Result, nil + for i := len(response.Content) - 1; i >= 0; i-- { + var item struct { + Channel string `json:"channel"` + Text json.RawMessage `json:"text"` + } + if json.Unmarshal(response.Content[i], &item) != nil || item.Channel != "write" { + continue + } + var result string + if err := json.Unmarshal(item.Text, &result); err != nil || !json.Valid([]byte(result)) { + return nil, errors.New("Browser REPL returned an invalid custom WebMCP result") + } + return json.RawMessage(result), nil + } + return nil, errors.New("Browser REPL returned no custom WebMCP result") } diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index f6a3b4a2..152ea22c 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strings" "testing" "time" @@ -44,7 +45,7 @@ func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { require.NoError(t, err) body := added.(oapi.AddCustomWebMCPTools201JSONResponse) require.Len(t, body.Tools, 1) - require.Regexp(t, `^ct_[a-f0-9]{16}$`, body.Tools[0].Id) + require.Regexp(t, `^ct_[a-z][a-z0-9]{23}$`, body.Tools[0].Id) require.Equal(t, "example.com", body.Tools[0].Namespace) require.Equal(t, "cdp", body.Tools[0].Kind) require.Equal(t, "read_title", body.Tools[0].Tool.Name) @@ -165,6 +166,48 @@ func TestCustomWebMCPAllowsSameNameAcrossNamespaces(t *testing.T) { require.Len(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools, 2) } +func TestCustomWebMCPForceOverwriteNamespace(t *testing.T) { + svc := newBrowserReplSvc(t) + ctx := context.Background() + add := func(namespace, source string, force bool) oapi.AddCustomWebMCPToolsResponseObject { + response, err := svc.AddCustomWebMCPTools(ctx, oapi.AddCustomWebMCPToolsRequestObject{ + Body: &oapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: namespace, Source: source, ForceOverwriteNamespace: &force, + }, + }) + require.NoError(t, err) + return response + } + original := add("example.com", customWebMCPTestSource, false).(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] + second := add("example.com", strings.Replace(customWebMCPTestSource, "read_title", "read_other", 1), false).(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] + other := add("other.example", customWebMCPTestSource, false).(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] + + // Invalid replacement must not delete either namespace. + _, ok := add("example.com", `[{kind: "cdp"}]`, true).(oapi.AddCustomWebMCPTools400JSONResponse) + require.True(t, ok) + require.Contains(t, svc.browserRepl.customToolsSnapshot(), original.Id) + require.Contains(t, svc.browserRepl.customToolsSnapshot(), second.Id) + + const noOutputSchema = `[{kind: "cdp", match: {url_patterns: ["https://example.com/*"]}, + tool: {name: "read_title", description: "Read title", inputSchema: {type: "object"}}, + execute: async () => ({title: "example"})}]` + _, ok = add("example.com", noOutputSchema, false).(oapi.AddCustomWebMCPTools409JSONResponse) + require.True(t, ok) + replacement := add("example.com", noOutputSchema, true).(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] + require.Regexp(t, `^ct_[a-z][a-z0-9]{23}$`, replacement.Id) + require.NotEqual(t, original.Id, replacement.Id) + require.Nil(t, replacement.Tool.OutputSchema) + + listed, err := svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) + require.NoError(t, err) + require.ElementsMatch(t, []string{replacement.Id, other.Id}, []string{ + listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools[0].Id, + listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools[1].Id, + }) + require.NotContains(t, svc.browserRepl.customToolsSnapshot(), original.Id) + require.NotContains(t, svc.browserRepl.customToolsSnapshot(), second.Id) +} + func TestCustomWebMCPInvalidAdditionPreservesTools(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 4e9f354e..81d7ed1f 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -88,7 +88,7 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Title: definition.Tool.Title, Description: definition.Tool.Description, InputSchema: definition.Tool.InputSchema, - OutputSchema: &definition.Tool.OutputSchema, + OutputSchema: definition.Tool.OutputSchema, Annotations: definition.Tool.Annotations, } } diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 9a1cd892..7d3f3f3a 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -67,12 +67,12 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { func TestCustomToolCacheRejectsStaleGenerationAndRevision(t *testing.T) { manager := newBrowserReplManager() - current := oapi.CustomWebMCPDefinition{Id: "ct_0123456789abcdef", Namespace: "current"} - stale := oapi.CustomWebMCPDefinition{Id: "ct_fedcba9876543210", Namespace: "stale"} - manager.setCustomTools("current-repl", 2, []oapi.CustomWebMCPDefinition{current}) + current := oapi.CustomWebMCPDefinition{Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "current"} + stale := oapi.CustomWebMCPDefinition{Id: "ct_zyxwvutsrqponmlkjihgfedc", Namespace: "stale"} + manager.replaceCustomToolsSnapshot("current-repl", 2, []oapi.CustomWebMCPDefinition{current}) - manager.setCustomToolsIfCurrent("old-repl", 3, []oapi.CustomWebMCPDefinition{stale}) - manager.setCustomToolsIfCurrent("current-repl", 1, []oapi.CustomWebMCPDefinition{stale}) + manager.refreshCustomToolsSnapshot("old-repl", 3, []oapi.CustomWebMCPDefinition{stale}) + manager.refreshCustomToolsSnapshot("current-repl", 1, []oapi.CustomWebMCPDefinition{stale}) tools := manager.customToolsSnapshot() require.Contains(t, tools, current.Id) @@ -82,18 +82,18 @@ func TestCustomToolCacheRejectsStaleGenerationAndRevision(t *testing.T) { func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { outputSchema := map[string]any{"type": "object"} manager := newBrowserReplManager() - manager.setCustomTools("test-repl", 1, []oapi.CustomWebMCPDefinition{{ - Id: "ct_0123456789abcdef", + manager.replaceCustomToolsSnapshot("test-repl", 1, []oapi.CustomWebMCPDefinition{{ + Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "stripe.com", Kind: "cdp", Match: oapi.CustomWebMCPMatch{UrlPatterns: []string{"https://checkout.stripe.com/*"}}, - Tool: oapi.CustomWebMCPToolMetadata{ + Tool: oapi.WebMCPToolMetadata{ Name: "fill_payment_form", Description: "Fill payment fields", - InputSchema: map[string]any{"type": "object"}, OutputSchema: outputSchema, + InputSchema: map[string]any{"type": "object"}, OutputSchema: &outputSchema, }, }}) client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ - Ref: "wmcp_custom", Name: "fill_payment_form", CustomID: "ct_0123456789abcdef", + Ref: "wmcp_custom", Name: "fill_payment_form", CustomID: "ct_abcdefghijklmnopqrstuvwx", Source: webmcpclient.ToolSource{WindowID: 1, TabID: 2, PageTitle: "Checkout", PageURL: "https://checkout.stripe.com/"}, }}} service := &ApiService{webmcp: client, browserRepl: manager} @@ -103,7 +103,7 @@ func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { tools := response.(oapi.GetWebMCPTools200JSONResponse).Tools require.Len(t, tools, 1) require.Equal(t, "stripe.com", tools[0].Source.Custom.Namespace) - require.Equal(t, "ct_0123456789abcdef", tools[0].Source.Custom.Id) + require.Equal(t, "ct_abcdefghijklmnopqrstuvwx", tools[0].Source.Custom.Id) require.Equal(t, outputSchema, *tools[0].Tool.OutputSchema) exclude := true @@ -114,6 +114,27 @@ func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { require.Empty(t, response.(oapi.GetWebMCPTools200JSONResponse).Tools) } +func TestGetWebMCPToolsOmitsOptionalCustomOutputSchema(t *testing.T) { + manager := newBrowserReplManager() + manager.replaceCustomToolsSnapshot("test-repl", 1, []oapi.CustomWebMCPDefinition{{ + Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "example.com", Kind: "cdp", + Tool: oapi.WebMCPToolMetadata{ + Name: "read_title", Description: "Read a title", InputSchema: map[string]any{"type": "object"}, + }, + }}) + service := &ApiService{browserRepl: manager, webmcp: &fakeWebMCPClient{tools: []webmcpclient.Tool{{ + Ref: "wmcp_custom", CustomID: "ct_abcdefghijklmnopqrstuvwx", + Source: webmcpclient.ToolSource{WindowID: 1, TabID: 1}, + }}}} + response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) + require.NoError(t, err) + tool := response.(oapi.GetWebMCPTools200JSONResponse).Tools[0] + require.Nil(t, tool.Tool.OutputSchema) + payload, err := json.Marshal(tool) + require.NoError(t, err) + require.NotContains(t, string(payload), "outputSchema") +} + func TestGetWebMCPToolsSerializesNullFrameForTopLevelTool(t *testing.T) { client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ Ref: "wmcp_test", Name: "search", Source: webmcpclient.ToolSource{ diff --git a/server/docs/repl.md b/server/docs/repl.md index df82623f..6be15e79 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -131,10 +131,10 @@ 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.addCustomTools({ namespace, tools })`** — Atomically add a non-empty batch of custom tools. Every definition requires `kind`, `match.url_patterns`, MCP-compatible `tool` metadata including `outputSchema`, and an `execute` function. Returns the added tools with generated IDs. +- **`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.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. -- **`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. A snapshot-to-action loop avoids inventing selectors: @@ -171,7 +171,7 @@ Every WebMCP request is bound to the active Browser REPL execution and is aborte ## Custom WebMCP tools -`GET /webmcp/custom-tools` lists custom tools, `POST /webmcp/custom-tools` atomically adds a namespaced batch from a JavaScript expression, and `DELETE /webmcp/custom-tools/{id}` removes one generated tool ID. The same operations are available through `webmcp.addCustomTools(...)`, `webmcp.listCustomTools()`, and `webmcp.removeCustomTool(id)`. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. +`GET /webmcp/custom-tools` lists custom tools, `POST /webmcp/custom-tools` atomically adds a namespaced batch from a JavaScript expression, and `DELETE /webmcp/custom-tools/{id}` removes one generated tool ID. The same operations are available through `webmcp.addCustomTools(...)`, `webmcp.listCustomTools()`, and `webmcp.removeCustomTool(id)`. To update one tool, remove its ID and add a replacement. Set `force_overwrite_namespace: true` on POST (or `forceOverwriteNamespace: true` in the REPL) to atomically replace all tools in a namespace. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. CDP handlers receive matching descriptors with `frame_id`, `session_id`, `target_id`, `top_target_id`, and `url`; `target_id` is null for an in-process child frame, whose `session_id` and `frame_id` can be used with raw `cdp()`. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. @@ -200,7 +200,7 @@ const [readTitle] = await webmcp.addCustomTools({ }); ``` -The registry validates MCP metadata with `ToolSchema` and requires and compiles both input and output schemas with `AjvJsonSchemaValidator`. CDP-backed handlers validate output before resolving their page proxy; page-backed handlers follow native imperative WebMCP result handling because Chromium does not currently accept `outputSchema`. Both packages are pinned image dependencies and may also be imported directly: +The registry validates MCP metadata with `ToolSchema` and compiles the required input schema and optional output schema with `AjvJsonSchemaValidator`. CDP-backed handlers validate output when an output schema is provided; page-backed handlers follow native imperative WebMCP result handling because Chromium does not currently accept `outputSchema`. Both packages are pinned image dependencies and may also be imported directly: ```js var {ToolSchema} = await import("@modelcontextprotocol/core"); diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 283c889d..197f0625 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -3088,7 +3088,9 @@ func (e StreamTelemetryEventsParamsReplay) Valid() bool { // AddCustomWebMCPToolsRequest defines model for AddCustomWebMCPToolsRequest. type AddCustomWebMCPToolsRequest struct { - Namespace string `json:"namespace"` + // ForceOverwriteNamespace Atomically replace all existing tools in this namespace with this batch when true. + ForceOverwriteNamespace *bool `json:"force_overwrite_namespace,omitempty"` + Namespace string `json:"namespace"` // Source JavaScript expression that evaluates to a non-empty array of custom tool definitions. Source string `json:"source"` @@ -6268,11 +6270,15 @@ type CreateDirectoryRequest struct { // CustomWebMCPDefinition defines model for CustomWebMCPDefinition. type CustomWebMCPDefinition struct { - Id string `json:"id"` - Kind string `json:"kind"` - Match CustomWebMCPMatch `json:"match"` - Namespace string `json:"namespace"` - Tool CustomWebMCPToolMetadata `json:"tool"` + Id string `json:"id"` + Kind string `json:"kind"` + Match CustomWebMCPMatch `json:"match"` + Namespace string `json:"namespace"` + + // Tool Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) + // and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). + // outputSchema is optional for page and custom tools. + Tool WebMCPToolMetadata `json:"tool"` } // CustomWebMCPMatch defines model for CustomWebMCPMatch. @@ -6280,19 +6286,6 @@ type CustomWebMCPMatch struct { UrlPatterns []string `json:"url_patterns"` } -// CustomWebMCPToolMetadata defines model for CustomWebMCPToolMetadata. -type CustomWebMCPToolMetadata struct { - // Annotations Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) - // and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). - // These hints are untrusted and are not enforced by Kernel. - Annotations *WebMCPToolAnnotations `json:"annotations,omitempty"` - Description string `json:"description"` - InputSchema map[string]interface{} `json:"inputSchema"` - Name string `json:"name"` - OutputSchema map[string]interface{} `json:"outputSchema"` - Title *string `json:"title,omitempty"` -} - // CustomWebMCPToolsResponse defines model for CustomWebMCPToolsResponse. type CustomWebMCPToolsResponse struct { Tools []CustomWebMCPDefinition `json:"tools"` @@ -6947,7 +6940,7 @@ type WebMCPTool struct { // Tool Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) // and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). - // Native page tools may omit outputSchema; custom tools require it. + // outputSchema is optional for page and custom tools. Tool WebMCPToolMetadata `json:"tool"` // ToolRef Opaque reference for invoking this exact live registration. It becomes invalid when its document or browser process is replaced. @@ -6978,7 +6971,7 @@ type WebMCPToolFrame struct { // WebMCPToolMetadata Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) // and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). -// Native page tools may omit outputSchema; custom tools require it. +// outputSchema is optional for page and custom tools. type WebMCPToolMetadata struct { // Annotations Tool-provided behavioral hints from the [MCP tool specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) // and the [WebMCP ToolAnnotations definition](https://webmachinelearning.github.io/webmcp/#dictdef-toolannotations). @@ -25624,670 +25617,671 @@ func (sh *strictHandler) GetWebMCPTools(w http.ResponseWriter, r *http.Request, // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y9C5Mbt5Uw+ldwebfKM99tUrITO8moUnXlGWmttR5zNaM4m8gfCXaDJDJooAOgh0Pl", - "ump/xP7C/SVf4RygHySabM5DtiSmXNGQxPPgvHBwHv8apCovlGTSmsHJvwaamUJJw+DD9zR7y/5ZMmOf", - "aa20+ypV0jJp3Z+0KARPqeVKPvqHUdJ9Z9IFy6n76980mw1OBv/3o3r8R/ireYSj/fLLL8kgYybVvHCD", - "DE7chMTPOPglGZwqORM8/Vizh+nc1C+kZVpS8ZGmDtORC6avmSa+YTJ4rexzVcrsI63jtbIE5hu433xz", - "N9rTLDstjVX5T2z66vT8Uilhwkm5pWQZd0NQca5VwbTlDoNmVBiWDIrGV/8aSJozU9CUuQ8FtW7jg5PB", - "//770+Hf6PDD4+GfxqPhz//6Ovn6mz/+8m+DZGBXBRucDIzVXM4dTIwqNXZvL/4/6DW9gM+E3RSaGcOV", - "JHZBLWHXVJTUMkOsIpRIJYcsL+yKUK3piqgZSWF3xColSMZmXMJ+zGiQDHJ685LJuV0MTv74GP63sahf", - "koFm/yy5Ztng5O+NPVar/bnqo6b/YIhk31ObLk5VXpSW6aepm7AvTNs7f0qmbijYhx+OUBgPNsxuWFpa", - "RowbXFpOhVi5jbUPxnfYhOsbnTHNMiK4sW6KzZFH5Bn84eBtrCoMAcAzMuPaWMIcurkJuWW52YWcbYA4", - "OOVcvsCeX1dQhIPbAHzYQxTaWi0N008LfkqFeHbtqWgdkikVApEm0/zaYcyCkSn2TciCykywjExX8P0V", - "05KJIc/pnJkhLTgxQMAn1TkMHcFqJQLUEnIu6Gqp+XxhSaoy5mHIlUyISTVj0iyUNYTKjKSCF1NFdUZo", - "mjJjRsQt3eDycirpnMEy/vKKcGksoxlhObdkUghqZ0rnY1rwsdvRZPRebpx4Si2bK71yfzNZ5g6CfrkN", - "CNaEl1G7k7VEoHzmurXotscA0PMCe/ySDKwupVtutnlkl7pkhM8AEG6FZMaZyMiSGlL1IlnJHL4a/oER", - "wXNugbL9DqdKCUYB1WwE/2EpxPKcGUvzgnBJ3kl+Q3KeamVYqmQGozmAUzs4GXBpv/t9PTyXls0ZsHP8", - "poZ2OJ4IuNcw25owYFKf23bu0nES+7GWc4fCjiQKuhKKZmSmNJlUaEWYG9dschOH2pugxAMlppzm3Lpz", - "sYpMPBOp6eJUZWySkJQWBcsIteSPX//pGzJdOf4t+BVzk+oVUXbBtGtlS8eeEHAj8jR0dDyfEW5IWlrH", - "kChJF1TT1HHHqRNyVK+AzJjMjDvVyWg0+nuFMz9PRuTp1Lizd3tuzuk2CnJ3FJNPWYk/jvMIMv1EhRim", - "QqVXJLRzPNUhL/IW7VaScyF4A7X8HLLMp4hI1QrGPEISr5w0YBnRqrTsK1OvNyFONGWEIltDZgXfGcKt", - "qZZwxEbzEZlc0it2UfGkSUImz6JndRyFg0ZZFl2hQyv/O+GZE0ozzjSZaSeDo4w1tM55lgm2pJpFJzWW", - "2jIC9x8uL89J0G4JtgL+O4oQ6hrtNTayBvlqvvapbyFHR4sXlqZXm0s8PTsnb0vpGM0ImlxqmjKimdNl", - "HIjkHGDT0HOAMo1r68jE/eh6g5CWSJoj8tyxQ0NKw4ibQdLcDZQq6X4GQa4pYLVdUEmMpFdsnFID/DIH", - "tcKNe7rQKmfkjF2D7kfOtbIqVYIsuWYEWV9cxgjxXDsE261YwG5m0DghDnV1roxFJaKlPqyzGlHm8jXS", - "xsYkf2NaDafUsIxgQ4JURJbcLjiqKYLLKB4kg1kpQW6/pvl2jTM0BGJKiGMYoGAiZgIHoVLJVa5KUzU2", - "URR2q+mxG9csshdsHd8N/vYii+Mefm6QY3R1pRab3d+9fem27PYeuJkfbcZFjFDXKKwF5sY6cboWSJL2", - "ecdIra0irkm0DSQsUBISQadMwEHB8oGoLFAgckNqVjIlKS0Ni/O7gupwMxPizWxw8vdemk7NEX75eUP6", - "wpCtxQAmwVLgWzPaAGaD5LYyosKmC3q6oEIwOWcvzmKwof90IrRm0GZBNaq/KPiRYyvJyDU3fCqYk7E4", - "4Ig8lQQY+HCueQaCOl1QklJJNLN6BVhLKJlpZhbEUnNFCndxsdaRTkKMgoEn1Yhjnk1ITldkygg1RqUc", - "dDsYJi+F5YVgZOIGgpYg/41TCAqtsjJlGjoDN9XXjHBLqNPgDKGkcKq0ZqDhLBfMXxv9F9yQgmpbYbbD", - "8mpRhBXcODlCXliSKWaIVJZwmbmrOcNtKcfoHBtQutHRoYRgoEYA49xAqI6TestMKWznBaY6iADvakLN", - "KKgFlDjWyh3Sq9KmKncXOK+QKZkyOIbGOf5EufWEwQ0efNISGhS3qWFhiRMcGcMp8H6irpl2vL1eSjXv", - "xC9zbJS4ZmNjqbYsm4BetvYbDj8huOEpAyBf86ykgkALDcvAq5M7aZWmpa4xAzT4wCodZtVb7Hs5wvXc", - "9XK05UAPd6XOu1JAhpodIELc9e7kj24PVrl+Xre4UVWE0LpWdW1xyzXL9wjA6o99l67HL8mgyV1vib8v", - "zm517amU/Roa7uJGgVNaBReSdUaVkNQxE9cCL2Se8L3QcPg2ZXbJWJun1henrvvB5dpEgdlHxNoE5swm", - "J9AAJIeagkjJ1raTCkY1oTOwx60tdUSeO46qrpgMrNogg80ZlU4mVbcVbOSI1N8EngC6gIlzSGU2XCiR", - "OXGGPdtrgPVJes3nQNx0SVcjMplRLkrNwh6CfDRW4Z0blywJuykET7mtD8Lvwg8Aiu7NgpbGDe5OzG/P", - "kOWCC7a2GM1yyiXLRmTieIQq7doKvjJ166Fg10yQpZM+0zKbM7DrOqLeNTadUpkpCWeER4MYx2Tm9QVV", - "2nAiGTF8Die/fccVBoI14IY64U2mDBRCWMqSwxrdOWXc5Nw4Ld2rk3AKpXSkzrLRexlwqBJma8eWgBYB", - "JlShloRqVUoHX6fNGG4ZaFHGciGIZo5dEer0HZ4hsiQgA+FP44EG+hTgF0cVpdAKcJxaslxQy0B+tiDq", - "djIvqc4cuZkyTRnD1Q+SiivjPhxHR4RwNIcnO0gG1Tls8uhkcDN0YwyvqZZ4Pfz7oOIoF2HU6pvn1fDV", - "V5fVPNVXT+sJf0kGSzZ1sBovlIloSj8oU6l0RbCkrrMi0Iii2n4YvKB2EbFvULvoOTj5/0pgZHhRZDep", - "KB2Ud96ZWqy/YYhoMfQ97BIwGgB+h4IZFEs4+orzblEufwXdan0fB71qp17VVLIf0B697YD2V6QC9kXV", - "qPa14bNVoZAO4ebk8Gvzbnfpub7DTlrahdLcUstdJ+hqec7l/AnJFAgGd1e9ZhE9bU4LfD/turT5q+Fy", - "oQwjGRMcVDQnivDpMXUcjmoG0zjJQqVlQaJsqGrwbDjuekhwMnloCpbyGU/xiRHf09yKvXLizdjP3r59", - "83Z8+vT88vSHp+N3ry/evPzL0+9fPpscVxZ+JVHAGbOXVflyA9YTP8zkJOgNmtlSS+CMpaFOlzRKwINf", - "Sw/baC39po4MY2RSA8OtuqE9+X4ZzwCq2L9pEEwdijnFqqFIrelI2MTpFCkTYk1raeuOJOfZEOZc0wNw", - "2/srAo4jocWm0/4UlMMhNU5Vc4rmhkUKjEBuzWCydnjWgaRbhXlPTWG5YNqLdC8LHadHVeg+lIWt49+3", - "vnA3BcHx7SY73LQs+x1ccZkRzQrlTiG8nyPyjsi5Vtc8axI0GJTwYQoZhs6p4B/c2UvrFVnD7Alh0jJd", - "aG4YuaaaU2mNg6Vmnt5JqoSghWGhI+OaXDMNziHTMr1ilhxdf0MekevfHSdkAreqMZXZ2N2qJnjfNOuX", - "rYrlG1S1Syk4XIHcNnHJ1V6pIRN4N5y0SWYRVB53TuF0rr9pf/ydO9dSS6fru2ObM2aZsSDFmgsdJAOY", - "I0pgkSO8QHLop+Rp8D8oLKp31Fz9aipdc9kHna6nTudZ30czlMVPan/lDp8EujW7tt5haiM2WPDXcNd4", - "1E1qE3fDaIz3ajkXEYsP3LW9DCI5lSs0M8OFOlXSlLljEHlpLEhgatw3YGD2C8T1ta3qTsQWlOvwjjIF", - "XzR+DXaIzPHEn9wmvMEnaTxrpEprJtCZrbLHh7eUSvmLK8GJt6u3XlTmWpWF8bbzbe85Z0Gj8xYEhe+3", - "Jw7UrZ3m1O+GeUsSt6Z6HQCPCw6PB3XrKSM5NwYdOCo4EOq0sxREoWYzpVnkhSUtNT6SBTXYdtjzfzNq", - "/kfRd/yJdzwt3Vnh4YZMGdiFvFbyCk8RX70dsbaeZMDMlFKtV0QqHPPd25deQW88EppyCmRvjh9Gj9pY", - "9n0oU7s54kE1+q2rRlnxtLRqxoV4Fb1w/rTg6QJPSs28axn1PYj7P4dMp1QqyVMq/NszctOMXVulhBkW", - "3mfm//0me/z1n9gfftfedEq1WyvNMrf8nqu91Hw+Z/pU5TmV2S0E7QWV3AL2TMKYI4uDTgjV8zJH2Vrv", - "jcuitCex5lxu2WxCCi4lOvQtrC3MyaNHc24X5XSUqvwROhgF/6JHG+M8mgo1fRQGY9Pf/eHr7Ovsuz/9", - "8dvsm++y3/3p2+wPf5xmf5z9kX7zzZSCQ/wj77Y7DmOM3Lcj8gzNEn5vyDG4ISnCkCwovr5YeE5wYkiz", - "jKag7rGUA3VwSQSfVqsstLpZPXLY51SkR2lWjGvQrWguYhLJH/QY9MpxqsqYJo5uLuDRhc1RDfXPBX7F", - "Af8ugYI9a5BZwELgEmDfD86SLanQ0CP9gFHhdFlP+JUh/3Hx5vXw7fkp4Zl/WaiX4/SlKSP/UAA/5Cxe", - "/De9qWtbhxMX8LaBxhjvgMFIKjhIdvd/Ukkv13vow6mSkqWd/pEvgjhFMJ6enRM4QVL3a20IdZmMKJk0", - "5W1WjH2H4K+QFeOMm/DliFwuld+EAXfy4MIHLiQBDO54rOOcFLxMgP9zg3ttxB58/c0fIxwBkWeLMjGl", - "6RWTGZEqa3nyeDmJlxp8BXJ472m6G0XAP2/bhNCg6UVGTgWHV0WryNff/LH2wjVPCCVCyTnTtbMuqNBE", - "M8do6jH6ASNndqGy5kVonUdFuWruOf5+bltrEmPTeeunAFQE6InT0HQ2IeBAjcQ8AZoNdGoXLDdMXHeS", - "rLvsYlhL9ADAic+HvTROuonJcMY4NxxIy8XZI6R/d/VdzJbz63Usa5qTP6MG5m5Tn7KiUqRkysSZWsIb", - "6r2IOz/yKG0NvUPodXU6iL59RF9YHUSz3dit/MtzSd/012EsB7n4qcnFzFPmeF5uQa5NeRj61SKxehX5", - "VQVZnO9ExdnnIyLah9hPTghl2P2KBzdiX6mAbQ/CYB9hcGCunxpz3cKeHAF81lypFxe6YPYUA/XNBf9w", - "v/zItMfuyZk2eh141IFHfc48asH4fBExqAUqINjAYcfZi/Mua0c3p1sjqM+E5yWDJc9ibysV2OD3HVBb", - "cpmpZXTbHnwEm2xoxjsicSvNsJ6hLz/+CXp8r0qZmfvmx82x+/Pjdq8DPz7w4y+RHyMV9OPGgs26R7gh", - "hTJAx24UTKVCUqV0xiW1zNyKxTdp9LNh8VYVnVBc3RKKHWLDj/pxhUY1FjiD3Mayj8u+gO4Rwz4uA0YP", - "eSlYRo4m+BQ/Scgk55LnTk7AB3pTf5iVQiBYwf3Y24t8DBSmVKh8XCARFRiTOl7M7ywfvSTsdAMMKBiy", - "GFWMpsXyVIgdchj/E5teKPAdAFZ1gqKPzJmxpWYmCQ7FkI4k41SoOeYd4XKeQL4AYpjwzA18x+usSCNy", - "quSMz4NreqAJDHdaMHL25tUjn0ODWE1nM57WaxV8qim4L5mSVTmqKv/oKVtQMav80wPIR+/lG8kaPlxd", - "MPEh4xgG0xAf+MYTWhmrGc2DgdBAGFmWkFRRwUwaZKZ3rYIYepyZGyLALUeKVS148MDdj8F3BdgyEyyH", - "EPu0Ca6kIbAq+VjJ2tr9P1UZprCA5qmgxvCZzzTnZKZrdcVYQcpiRJ65eQ3kEXGQg2dscEVZlzKjalXj", - "sNbJkyBx7fYOmYa4xI+dxWqNQA4Op90Op6BiAKg+gptp5FiiGYYCzbV9SOulBs9Rx4dc3xxkHJDQBClr", - "MiLPaLoI/lPgwsaBdwh006rJhxaFVtdeSUR/N5zlpH7fhS9LkRHwlKLEsFQzS/7nv/6buM1mmPXN4ZKX", - "wZbd2IS8e/vSJESzGdOaaZP45ComIZblBfiCes5ZULtw29F0TtJwZQOGHXyV/FrclN4VS9AU90yJAL0h", - "cSzT0bH7Az1QU0Zmgs4TCEuRZQ7OmeDCB+jOYI6FMj4HUAOcmLYyp0XhUOHkX5tP8r3f2SO+TknXw8jO", - "QXe+KydrRs3eI66/QCSdNoO+Q24xJyad2uoeg3fdjZPB2ZtXo5lKyx7Dnan8uWu5OYBJtRLihbTqL5wt", - "X8xeQxxxrxEvol0jUzD7nAv2wqka7o9+671Y79UeGL4PiKXpfA4YvGtc6HXa6hQbNuOmcDc018arX71G", - "Plvvt23wH9nqNmOHbtuGfqVKw24zeN1x2/CXqkwXtxm+7hgbnuWlY5fQ6LlW+d67eNY5QGw6njOg3Yqh", - "95zlxXq/6ODSMG0v2U3ftb+oOsSGMytpF8wpFudcpot/R32959AX0c7bp0Havu08rd7bJ7qkxW1nqbu2", - "pzinczaaOu3mUj3Xqg/6uC7fN3pEBvS+XnXKxX6jnq532zK0pMX+A/tOsWH7iUYYbFMuwhCYc7JOpXcG", - "V8J+Y/4Q7RuZJASx9hv2tW+9ZaBL9QM3VunVM2nd3WOfYdt9I5MUmkt7qc7Pnvcb+Ny3z2aRwTCJWb+B", - "3rJNLQgGccoFm77kM5auUsHQMtJryItIz9gElmqLGJxS0xM/L9qdosOq4qWiWS/JjUNWHTqG23+RzT7t", - "QS+pnjM7oqnl1w4x4OPuYbHd01av6MBAnvuNelp3iQ+pGbUs9EKnvN5jR/pumWTPhTf6RAd1qoUyt1z6", - "WaxzdBpVMBmeYvqO/qbRpznoL5WxY4UZSIOF75dkoCTby6bZQwn7JbnVYDF1cc+h4qrJvoNs05puube4", - "OnnLwaI6+55jdd8s9hyonya756C79cBbD9ip8N16xLhy13+4XTfIvUbauDvvt46d9+T+w21TVPcbZati", - "eruhIqrofgPtVhn3Gy+mK95uhG61cL/xNpW4/frHtcn9xtiime07UJf2tP84Eb1uTyRcv8LsuYYdqnD/", - "0XYpgPuO1KH07T1Mhwp2u3G6da19x9upvO07YLe+1necnebn/Ye6PXLutgTfZqwuk3X/sbYY/n/5OfYe", - "9Kry5dj1kH16dh4eT/3b+83KP/kafLA1zIYI98aLqSFUEjpnMlYfCLIyPFl/dD178wqeR8K79FSpqyvG", - "Cnjvdj+g+1Yd4P/uRWM2DcmyDc8YeB7VHmWNKlUnvWPMO224nZbjiLWx27q5xYrbbffuMLT3MNnuMF3u", - "MjlutRR2vSs030S2Pm/EjISddr4OK912E9m6bWu7iaptDtq0NHUYYuKWj4h5pWUR3GY26jJ7RK0WcZPA", - "dmvEjht//Kbe+ZK4/g7Y/eLW/dCX9AtEbvIzcIHo8suRhN34ioHetwUzqWAVO6bJxdmPzUpjCTkvi4JZ", - "xvRx8B2sPR4jXjvoHMMN+curUV+PC++QeC9OF/XuD04XO5wuAFQPmbA1ch63yHhfO6xGvDG8b2p3MbH+", - "XrLcRN1kG/6w7qgnrSEnwRcs5hyCbhu1Zy2I4Yf2l/1l63mcVT5S/RlE7VfFsjql6w7SJ+eU6zrXFM9z", - "lnFqmYAyKinLNv2LPSQBEOjr9iswkDUAHXjIVh5So8bDspHYqezPSerVbnKShk/7gZncQ0nCBtfuU5Uw", - "Z8bQOdudywhvX9DYEM0EXbEsFGPanNdnC8y4xu/ixc00oyZW4OunxWp9TKgDMSITBPoYo7ZPmpEccLMC", - "usUvlWEjMikL5GjjdEHlHFInw92Nlzk4sWIORMiQ7N3+g18yYpDFDIZLWQWi4GzoE6hZwGs6p1wajEGR", - "bEnCvM0lQELoyUn1G7hSE6UDXElR5gWmkMa9+lQbVToDv+FQVzHk12ilPCBHdlW426ZYhWKRZlFat4Xj", - "teRlDVAOksE6pJpfwZqgjtvaiuLJodedgLfhlSmLkEUv7qTtCXDJsGjhkuqs1oMDqU1LG5LveN9nliWt", - "Uh//LFmJ9TFmpQCor3lJF1Ty9MoBHgOmuEw1y70ft88KAe7cwUX1f/7rv0kp6/VDlszg0o2GCjQGzLiw", - "UDVxigkxaQaJMz2u+WU7+iNWue0hF7PKUjEil5VjuHCkpqRYPfGFdJoRKaGwCNJlGzoj8lQs6aqqRQNM", - "DMp0Bgd4twPC7RPM3tlsUGiW0aqOJUjJhCwhs573h6/YHjXkA9OqK8Bj05d8G1rUJ10RXBMbfL7NjEgV", - "Y9rNk+e64XLv8xqC5zqi2SjNilFY0dgf22Q9aqFZOs2dA8SlcUCN4D6PTB7UtEmXG/yOWMkmz19nzRW3", - "3CGwI89St46XrMwmOyIkG+0OMZF7JVXCTG9jqTLWJyPc2ZtXG1nhmrFpFqwkh2SBn3PejtqWGZO9uzDp", - "rhiEPGfbDMFmrlmuLCPYYed0Hy9R05eR1aSXg8GdBEPcht5DUnR1PIiOg+g4iI6HFh0dT18HWXIbWaLd", - "+rvSJPyA+RG8XcY1bS0XDwJlSBt5M1VOIT36hrUG5uuM28/qXPr3N9vN5kx/JWo2M6zX1hK4k0LlMk/t", - "DmP2W8Jqcwn/+fGW8OXoC9scI++mKqyP3EdL2OxzUBAOCsJBQbjPRPSih+HftTKQ7qM+ECDUEXlexa/v", - "UbCgQy3Z8No5aCRf8u22hZ07RFdwS4uX7HE/h1QwC2rYLWq+hPw9xFgwQc8glQKeV/D0A7O0YRY4WpW/", - "xqdeKUoLToh6yi3ks/HVi0IRn/Ds3nqfydy+pGV6kMDfb679n6rw36Aras9iOQ4Or3jOThsv+WtcWhX+", - "bF+9ePWMhLdiqGSC+Sm4ZXlSuyK8ePr6KdFszo3Vq5aNvJkc6Umz91eGmHLqlumLnuCzgsBi51WOjmru", - "RiKlX+kYvAMavIcMkgEtM64GyeCaZ8z9S4tC+CckkC9gqs9VBueSl8Jyx5lrc37P0/I+jejMEEds4JUE", - "n/EJJbVLZBYyNxHviGk+HZxnM1oKBy6rynQBoCxNX6DtiLa6tQ4bc3bdocLGuxw02EPGyC/L5BT1E/9y", - "k43vji29I5Pa8NXvxacivQ6s6sCqPutqM5rOx/vcgH1tftBkvRfjLW/AMLVThndP7Vp1TP3CsryRMG6/", - "2XOes7FXsj0Kr92auLFcppbY6KUAEurN6lXhOjGiYeI05klCJqAyuz8aOvLkeEQu8AJgfOq87h3cNddq", - "MoB17eGEvH5Hqp26qdZ0VcHP0T7sx4xzaq4i+W+59T7F7vYkhFo6luNAVXclR1//OVXFKiHf/FlweZWQ", - "r7/7c66u2XHX2cE1tqqHvG+K3PZFeTNJ7vpN+YRMqjuoO8ZwCcW/VYF18eqL6OTuyXG7VYjNILSOuoCg", - "G5gdJ7Jw/4bG5Iqt4DCeCuvO4tRqkZDf//kVszQhf/zzxYLPbOeZfIrppCOPOn/hbAlugDeN3NGO85xe", - "XJCC3zBhej+ZrLYMv7rr8F32ogZt7KGDxRKh3JMKFobeSwOrOx0UsL0q85ZWjTUrGI2lm18wb5oKSaEd", - "1c2ZdJwYE6leMYglZtS26LwR5RFUvJ0ymznUkfPaY9WLRUyR/VSIybFPSltVDodFHR5WPhfd8m5iOrCA", - "Din9I1utCekrtjpTS+nE8hVbvSvcH5ouf/Rfg5R2IuJe5DM34yu2Kmi2ncwcPfEqYbssc6Z5SrBnF4Vx", - "MzYr4/TiK7bqQ8XgmI9dYMJNMmqMLpS30m4M/CNbTRXVGQlNnC4g2AyUAR+M+7s/yzIvaHbc/1mrIzr/", - "N6K2SJqzLA5oh2HNbNhIkAWds7aNf1VU9lwewpknlbZ4Safun6daq2VAz+ffOv3/R7fuphs/obUKE7kH", - "PCF2oQxrJaufrvCeMcb8123/+k+81ke9rW1iph637fhSTnNura8tT7AArzVMzOBpsccV8X4VrHiOsntS", - "serB91Kymt0OatZeTiWltchC95VqAPPvsfumUMMf4EmwUorC3o4mTs1w7MPxZCzVkmUCvgHu7P6Y0vQK", - "arVg3NB9FGpJ/G53MWXfCplzppZyRF4rOfzAtHLyj5IJvGG9Utcsm5CcUYkU6276KMK8YccuOlVAwdOr", - "3ZonhBujNofwhMhAAWFt5OgbPxncq/Dr44PO+dnYM5mwNOat+YPS/IOSlgrvFEmgaYIh2ICaPy0YE5Pe", - "13ucKnbJd4wivb+J7qZH11y+Q5OGBmu6NCzzHAU+sBr3+S0TjDa/8LS8vq97YDkz5dMdrNE4lKkCEVi4", - "xZWaJeQxqBi9oblLQ21kafqN6KiFch/0rTHgHPt3HL//FTXR2hhQXy+O8HDxnIv7qf71iaqkVM7daqgY", - "B/TbiqN1+wa6Dr/eD18tF1H383MnFLiwtR/Sfw7/RgpBJUtQnZtrxsx+86z6zPPXu82z5MbGp4FUDktu", - "GNHK+hx06zNsksfBirs95fM9XTLqwfe6ZDS7HS4Zh8f0g8GzM0FrRSkdkhoarClq4B8IOYudVgafnsms", - "+ttpaCi44eM9PlD219DCC/mMa2MJrIOASnPvqlsjaedvRHXTNOOl2XEfwUadYOofJYaTbbuR3NtUXj6P", - "qZyLCBq89b9vOftbqA5fqM64BYj3pUxeOgWva5qH1jB7Tn5HtdMNOYYhd1uQIL8ray7DrAlVeLfszPET", - "V3FPN9TbPcjjDnpvJ7UTlCiNItlEuH1zCX7tKDPgfmgmXrHgxokFFBs8p3rlT6n5IkIDnDwxBVXn/lXu", - "O/KxHtp3DHF6aOT9SpTcUTPvzi/dS0Pf1v2gqf/GnwPuQ4W7B3P6wXD+pRrOH8hM/ps1ipMpc0D3TBOo", - "CSTDQzp7bikgcPD6PHh93tpeuLXc3B2Vko3SGr10kUivgwpyMBZ+iUGCm7Vpfo6mXCoETSEx75jJCKTe", - "1g2IpnLOCJOZTxfUaa1qDAo5ofsMCw13DIw+v+5Qo2u9CD831gi5+3l1mgEYu2foWHg9R3PBe8/yJfjR", - "ReBx3050zUX1EVjRIqt3lVTVoP1EVKP5QTYdZNMXKZvqUmo/f7FuxgiEB/As3pMp7q5YfEcGGS9O14tZ", - "dnU9MM4D4/ycGafPODTGdES3Nk9tpj3atFC18h7BLrZmPno4K1VHDcv4rQUzkI5NwVj00uIzlHqXRwLt", - "4DEMzSSkcF9CIZ9OHT2lgo1nNLVKb5kBmoUHpMItnBy9Lx8//h37mnxQCtINHH/WL9Ofnx1sL6HZWZX/", - "3qRma4Y9xeZa34PcPMjNg9z8XOVmu7hzTHAWGi38M+G+6IwJhp+xEFZZFChOugKBMcy/hy9MxQt9ZoDK", - "fQzf7bYYE934GRN0Fa3ed+Z+IVNml4zJMHayUa7vMzLFdWg9F0teMFR19tJ0HliA3zgmYKmMuXlGHoV9", - "2/ZkTwiaEK8Dthgi2MzusQaoYw9dI8Via9Rc1AuqVlIlvy+oL0nIAjB/bf0lGay2QHfjZbwvbMtij/l7", - "QvY6LOZe4HoPiltd6v5BtLZ6+D1VtmbHg7520Nc+a28hX6IxItVDrcgg2MFLBkrHQi5496kseov5L1sx", - "rFnK52Nvp0Vvl0NFLC3WI/YtLY6/nFDA7ZKxlSMpmsDmU8z47xM5DZIB5HEaJIM6jdMgGTiU65kJvele", - "uwEedPGbrvnY0trD9hMBlxNRg2Tg9GpHgJAUxMEMynElUAAH8W9JdbYP4Lag1oZ75CcDrWZmg5BXPyQ2", - "CJ8hr0H4AEkNeoLtnM7Z9+7rS/Vcq3ty9neDjqaNUXfopZH2B3X0oI5+Wf4KG0TwBWfbd7A4RY+Ei1Qz", - "Js1C3SNrSteH7sOfIp0OTGovJoUAHE/ZSslsfO310O35In0nwm4sg0JmlQUn9O9M+yp40Vni06E0FEBS", - "kmCbWyrYMAu8zG6fBJIw4gsuPvTuN0NH6dDmDNDkLru42T7+TXBAvcMUq+1TrO44xUEKfmqFDD0w9jeP", - "1Gz4OY4RsY7kjuZwCuJEEDOOpx1N/lGw+SQhk0LOMdPEkk2L+8kR5W4QY1PqGY1ZyWOMzUlkS6+YrMPG", - "ff+NumyO43Vxuw59YkNoRZUKVVie8w9sPFO6y9ElrJ3JVGUOE2b0WmmW+Vcgdc00MfwD61rgP0squI0x", - "AJXDu59DW98opNl4/Bhz0AllzMqf5Gf0vHZXzUjS4mH0Ij/wPlpR1eWgEx0ubgeRFRVZnkY6BVZoEJNZ", - "+cLm4n5k1A5R4Rfxpd8+hTLsHjmrG64XP8WGBy564KJfoPkLsP8L5zw/UJkJ9h/0ml7AHs84FWp+f6xo", - "ER2/D2/q6nlgVnsVPEpTVuywemUAWcRaaI4LyrjJ+TYPyQMj/EwYYZzUOhxtVV7Y8f5RgUxapsGB0ypC", - "CY7jUe/WkYKfD6dOAqXuZtmv6TWfU3uP+qL0I/Zhy3XbAyM+aI2f9d1b05xFN/mmoP8sGYEGDc6yhTM8", - "IZQIJedM+ws1xxu0wzxHYfUYd2LkgTg7ggtnTGtHIErwdHUbo8JbP8Q5jrBpVAgNCM7xYKn56r1o5nB4", - "h/W7nZYUoI79UfKET/2kT0MD+iQdDDWVmLXk9pm4qyE6XEW9iHJb14wa+McfZcAFKgTToRqc4BLKMrkF", - "QXJuzYSi91SeqdRiDHuIvJFcwPdVjWpmLJe47ndvX4bVgfyCAtVTVUIeccf33eIQeUyVndd16kCexrn8", - "7pt7u74FXeBS/cCNVXr1TFq9un/NoD3+PnrCes+D1nDQGj7rGgcOzaOb9HRAoEVrO4FWQAb0Lhy6hcw+", - "a6NWA8a7GeS55tJeqvNsdn9MsfBjnj3vwwibrQ/M78D8PuuwJ24KQVfjBaMZ0+OZUpbpbt2cEmwI68XG", - "ZMm0Ox6ZsS16N7YdW5YXglq2+xZAw/ChSxUELnidqcr/tO8tIBSFG2cqBZQeq9IKLtm29YS2xLeFBbF8", - "yrKsx0yWzucsGxfZbNsc2Ioc0TR1vH0q2DE5P3sOU1XPvV1z+TPcB8b+NB8CxsLJk5QWO/ycwPHRTSso", - "zxxogRGFvp3uTFTPuRxPlbUqjySHhu8JtiLwX7rYo7SGHx4CUDYGf8lm9s5D67jf6VtwNb3r4FYVEf6r", - "ijsMHNdlakkZNz3TORtDTlXTBxl9IXI5X8PDDhwoaMF0pwfvufu14bu754Zx8A63Why7cqjdd2gwXoxT", - "Y8YAIMM/7KAR8HUFB2H+AWFTeAOId6xDQip2uNjBaY2nNL2aa1XKLU58dRsy17RY8NQgm4chtlhX4o7O", - "53CwICHQx/nzro+kqTTuhHOV3a66aTa79GO8ckNsWmt+UEvYZy0avCZ0NHkLfz4131PDvvs9+q+G7y4g", - "euw+bDS3tX68BSvR/Sn23urUQ6kPLQ8K/UGh/5wVej6XSrNxStPFDqmCFEGmq4IiewQjb7rolB+uOdPb", - "3lcqFRmb/qZeWnC7cTsL7ORWKcW5dIyOZQQ7HJzPL5j9iU1f8hlLV6lgF/Ze371NZPQ+3D/e7yALDrLg", - "C3QeihHDZ5ONxcBubqF1b4JkU++uGhCYhlC8oZKjyUyrD0yiuo21Nh9OzQ6b7MGMLdUWg7tSau4xEsi0", - "B+7Fgte7HLjvgft+5rXT9Wos7WIMbkebe30O3kiG5oXwSV6Yvqad6UfvGgzrqK4ztgjX0hUMC2ytkPN7", - "CjCiN51mu1f0hudl7j21avNdM9Q78tRKb7psde0BK5vdjvE6BGebhUVl5u3iWA/3hguripeKZlzO71NM", - "VYP2E1GN5gfxdBBPX+LloKaBLzzQynGkh9Gdm+P25UsHzfnAmg6sqdihfX0h3GntgTCSzH5JKAm+Jxm8", - "E/L6mfCTSW3ZfswcJIP2S2bfHJZYeime99P/iIuvIUYlsmDMCPpp5QJ1HJDJnsBZCxCJlJfqiA+hwQkW", - "qql+Mpllw34GSePDTwsmz9RSzjXNHPiU5nMuqz/cz6daGfMmfO94Xf3Bap7a6MfNnqU0dMbe6b4JWDfu", - "7h1mhI2rOwLUVN0/mTP6R8HmDoXlfC8QNVKnbMrGeKqvJohc908TREs2LfpCqp1iZhOV4hBC3yPf99Ph", - "hQub96WyS6rnzD5NLb+mluGne9H4cagRbY28Q+fv6HPQ+g9a/5el9UcJ4TOqG+G2s82dBlv8Gl40nQWd", - "qzVvv6F4DiqUeQBmmtbD9uOkrQ4HNnpgo18kG21QwYGHfjo8VDNqWfhWSctu7peZRsbvyVWjPQ/s9cBe", - "P/eASWXYWMlxxixNF7sSmUjMkmWI75nV4M7YtJzP3RoDb8UhmemfxnsLMXalA7tZjdHpeyy4sb0is+Cw", - "sBNxnYDbp0rO+HxbDCBOZpi+7pP4JcyzNjaEIeIvXr583uleSsmvmTZUjCWzS6WvxhgNOkZj5O5ac9jO", - "kLmm0nGsakDiByQ4IEI2tLYLlhsmrpkBAumTzO02bxdNqfYAV4PGuPtIscPl4DbSq08ooSMnr4MBXQO4", - "sxA+UY/QRcEBAp6RblP1An+veO6vEX1ykOifXv4XOhVsPGWOuWIKOYdBWolurP7eNcaHF98WswHAUBk5", - "WjCaCSeplBSr4+6kCGlptpOOZMsm+UCPbVkW9NjS6fYMA9MwYLN+iDfyb1Jqd7KBuAPnT1xmatnw3Dx7", - "cd7l3bjgWcbk3rwDu3VGqUWD9v2ybnyFcWSA+PLTKHO6h0NoRHxE9S3JluMlTL7tVNwxY6t+qR4+ybt6", - "LCuBP5fVLc/lU8gUlww6XJP93iuf5C2Ugrgxvn1wC/TvCmvxC8GYlqbzt3RyQjio5FxCCSDI9ZfTm/rD", - "rBQCD+zXCixH6jvD69XDWUqy2AT9lMyOrgdtcy9t86AJHjTBX8d0HqXfzzxlX4Tc+rDhNwWTgSvdJ/dV", - "jXH7Md12jwOvPdilv0je1SSDjixdkokoeEI3Ak2IGwmchz+WBD28SP7GXiRVmS6eXTNp477U8Dt6TJNi", - "QQ37ZHwFrVs5RMs7cMA2ZRb+fKWuWfj7lMqU9fYqbGdd3zTZ90y6/smAUXB55fkwFEhB4htPqXafSqvG", - "U6WucqqvwmdTTjE62iGvLKloflO544fWVhVAr575u7Y5d0fmk/skgyu2Wird+GvcHKTPoW3mgYjnsxPx", - "XBCfzFlhxgooY2M5IHgv6DQMCZtpN70gW0btCZ9QkIDOqcOxyuiBrLX6uzZ59IKakkYJ9kxrpYF5bgLu", - "afPG6hoT5lo7/a6UKS3nC0vqYk+E3aQMuobolGc5t+CuDSWil4pk3FguUwsqjVGlTpkhS24XJOOzGdMO", - "RE4dJGZBC2ZG5G0pLc/ZyM//9PzFqWM9GTny34xwRY4hmWOnJWWlGxPIMYFKUIlTW00CGpCxNL0aW01T", - "Vo9dLftyodVSkqNqb9UvzaFxTMElS0iqRJnLxG9lXGoRmec5ZyLz8tERY0qnggW1E3uCjkVBC4iqvdSy", - "udKrphrl9x894czfbPoY4daxAG5FTsmBhfUcBXpeYA9INelAaGP1uC+10xa87dOd9MxBB8uJh14kKwE+", - "kFZU8JxbM4qam23klQKWQtzBGkvzwl073kl+Q3KeamVYqpw61U9LD1VN1kA+BpSLAH5Nj7EmjJrUJ1gB", - "dptKEz2Tfc2r0DPYBH5J/rVez0/PI9B7KkRF69V1jKSK6RT1QNyrGZFzdJuAlyS8EeHGPLV3Ea4DPbcs", - "h7k3tVT8gmpNV3hPcvQVS1rhvieYkDU82mIDtxaNht6aHwF6AXX3XvgaVxhFMQSlfvQSEKDoOqFoSAgV", - "S7oy5P0AMOj94E5Q3ABePFH4Sy7Zrw+omkFurvDd25fhMcOvbMaFF6B2odmyvcZ7WFgrLVdg1H0ZJhXi", - "wvUCfHW0tRnmWuZUDjWjGXB6lFBeFJmWhSEgyVKVIiM+6T2YGp6r6lcUcUfHKOQSGGDGtbG1PaZBoNST", - "KA4REWWJfwRhM34DwgrXlzNj6JwlBB6g3g/ehZ7Ahk7IVKn8/cCJ/sZvR1xCAUZu2DFxlwnf+Ka+EM5K", - "CQaL94PWI1IXz2wbMANr/HmDOb5U895Ki1Bzf/urtAah5kkFXy5nqv60pFomhNl0dDz6FSRx2NhBDu+U", - "w/HSovcshVvn8duSwXuJ0i2iqlPJdmMkpMrDq1U5X5BSzrjAEqzAbtEKPSIT4CMTeEVVJdYgIi2VCYnQ", - "EC6NZTR7QqgQBO4pZF1iGqcqM6qJk1EjcsHQCmoKlsJtC3hgKQRxOBFlLA/E258D410/ns3TGe1kdcFi", - "sJvltbCo83aLHC48LQLR1QabwBJzJbl1NzgoniuEg+owSE88nhFZMz+jGS7BVGV4v6lD7AkrVApuAssF", - "TxcoqmElKk1LXfkitBG/uyKmO+X1cphwQ/S6Cy4mrv9054F2o3YngU6IUyicPkEYTRfNBAKxeSS9Hhv2", - "z0hONyWVRUuCWBEuU82ocdf6BrgM+2fJZBpUsgSbuXWB/R4XYFXhDcCNnlEg9OCet7BWBwrzJuH61SEK", - "jy2m5YCbG7ZlcmQsKEfUSQPT2KcJGwWfsONtMwa50IOy8dEBi0yCe05cDdVMsGvq5JbCBy5A5SfohOYa", - "OMg0zsTRAvyGpJMEq1Ld1ns2e9LayRQah9UEbHvLNQpuEV9NVWC/R85zra6ZpA5Jc2YpaAf+5FYOm5HQ", - "vT1EE+aNPBXlb2pNLK6pnfshho6t8xlPPeeQjvy9I1SXbJoAeJvcqzJRAajjiHPFYz7BqKqEDY3gnWxy", - "4gUbqV6bzoPfkBdjaNWqmeuITK6YlkyMacEnJ+RH+ECenr8gGGpAjhyf0df+RRG/HNbJXcLKyYTdWCYd", - "IkxO6lTufj3VbyMyESqlYlxolTJjJifErIxlOfFfEF1K6U6MCiXn3ipZL7dlXEyzAozTYf3upzDRwPHW", - "xkRRTTegSjeyRZSUXfgQpBkig+NWSAePPJ08QlHx4qx13oEW1mgLDn8LxfxgbfEDFJ4y3ZuwutwgmB8u", - "L899ySpDclq4011SnYEb2ZB7THGrd6xNlZagKZd/8Flq/oJmZ3hoXRVefngtj0xLS3K6IlNGqFzBwzao", - "SC2tZ2MzL6RlmgLTPhU8vdp5WSrhxuSaBk3C+xKSa05rJMScG1hUoNftiNcLuesNKbqnwz2p857UAP0Y", - "TvYBb0vdZ3PPdybDBEutihQDPL24IOFXUlC7CDZ22LvjrwIUrQ6VYh4x5Fy+ekksnaNE8jaqtdHcgZVF", - "wXRKTZBa37+7vHzzOiFPE3L24i8dOkxUmf8Lh+p6YC1C7idtx8QJsZrneYcx8CY2NlsWSltyM6xdmFuD", - "u71AUS/MQhxFstWWgVe3H3gND28GbqakPm08oa3XpAYK/shWOxneFVtNFdXZp8Duwn4OzK4Xs7tiq4/D", - "6lrncs+Mzm1iA4A/spV/Z660zx89HiNskQE9c0tMyPc0vTIFTd2tPc6FbsFNA98D+/yCZhj8UzvFXbFV", - "qAVoTAd36s9tfWTRNm774vX5u8uEXD776+XTt8+6ee66OsjuwGAuUq2EuGDWCpbtZDUGWhODzT3DCfcm", - "OrN1kyrcxFhVGJIuqJxzOU9+2+xpExoHRtWLUeGpjz1ifBye1XFY98y9HHsa38SinwDPb4YVplPrPV6o", - "to13QNdqzoxD+j5qCcy36pxvdd/zeXvMLfgnzrVLHVUx4D3nkoqw2CYIZ9b7mIYdBFbTZycqBrfWVKt7", - "mWoNlz2GVEfnN+0XtAnhraz5Jb9mTg09RVNlJ0cW/JqRa86WlUMWdqj9wN09flaKwLu/MuQnNn17eVrZ", - "cF6zK3U8Ij/4dkqK1RN46wwMfaY0qQJteU7nzPR+SPR21rvy5hg4Diy5kyU7rBg7rAj+8g/IiTuPZk8j", - "LdPDYLkv6ApKZjrEm2zsZdIwPq9fpbtfBl5WhLL5PjAiFy3jvWZ+KuOdHaHUMJBXsH9PBS8g7sIRCbgC", - "eiMqOP9VIQeTekmTvYzlPQB+VgU+9OcOdbBE5cW4B4s4pxzfrqKnMl1tbPfXYBFrYDlwiR5cokaLj8Ao", - "Ygd077yiERXUyS6yUoO9epzHkkNQIYapUOkVCe0qC1AdtMQlybkQvHEQe9VW38aVnmDok3+0TpXWzBRK", - "ZhAL1cUV93yRa4Jgy9G9wlf2swb36OA5lz68qxnYpcJLj6MKoYwdkUvQFa1eBbbpHwQyrSBAp5SWi/C4", - "P674MQuhYWZELjWjFl4QuBwWWs0hY5WjafDVQKf4o5DaiWcCPD/mbCzoSpU23FGOCTWklJoJDiIAZ7YL", - "JvsxML/Gu3KvLggf2Fcn+wrY0ZRpD8i+tp7QLv7VxiMMQorVgIDgpOCtUG8MHtVSIKKxZnDTY1n1oFu9", - "joZfRs130LVeuyHkV7cbFC8kt88pFzuZQeBtKXiFuqvF1N1JueVU8A+43o9NaWuLP9DZTjpzBzaeAcge", - "nsxix7MfkRnLim6UxJhMonSNh96fybICTcG4VW+T9aqxKfWMpmwEIYw9bLKwiN27fRsEXC9yisrWFm1p", - "NmTgj8TNorLIspsFLY1F/wlRX3LQhmRZXlgzIq8VmZUa00KtC+klF8ILYAIR29wE2v41SDgGtQMd76Tj", - "6uA/GjF3HtSDiM0WYrstlpqN6m/Hng6cAEU6cBgeCIAsmWYEXmjKonJvMSUk8pyVQqxAzCodMgu0CbIp", - "eSMz3qPwfcvurIqv7SrCMui6DvIMGUGwDGZlBYc5LcDfB/X707YaDomoQuzomrthsKhYTdMrN5pXVchM", - "M7MIRgpuSKG4tL8qnznwmL15zEdlL3dhLYFW+xoFHPjWr//E0isGVFaN13hfaJNSH/hu8IbYInfDp67b", - "1WkoLJjmKuNpo0hXsHaEN99r7xTTjwLrce6JCNc2caDBnTS49QjumQRjp7MfBRYy4kGB9SiHTKYqYxk5", - "f/3vPRG0Att0ZdlOLb2Q8217fI0S6kUm2E7PiCDNeBY8t9f8Iij59vHj3JB/lpxZT3doU5eKcDmcCUjo", - "Ci643vm+52ubn/qu9Lb2Dn6gsE0KaxoVH5C2PN75KuFbr4abCCiwV7jF+gQWL2ZeRcaoDsjcJDSj2crB", - "x+MeeD45zZFaqJKQkSOpSKG50mQS9u6HmGA+5sZLMbfHCZmUGtKZhrgo93cVzjTBmKuJZj6K2gFg0kgZ", - "8YRMIsgIkXgF1e62LlakUEUp6hzn1JKUGtY328Q9EUvnER3k007q8Rj68LfQ7Yd0z35CKSSu2nVmTQIM", - "PdZDG8HNpuUOt3l0EIY6jrtevw6hWhCq2vjNm7Qksycnz96+HZ++ef362enlizevx2+fPX938ews7lvp", - "F90ZeBc21YiKq5L32aoCBgUL1Bob6Xy8crM2uER8Yr/T0Vvf9HJVsIY5AGbYCPttRrL4iN8fpVrKkDOJ", - "y1SUGSNnPswyIc+ZTRcJ+esPbxOCGYIScmFXgpkFc3dbqH6bkFcs4zQhz5Xrc8lu7KW72SakQd0J+YlN", - "L1R65bq9opLPYIXnms1wjjd2wTSyyVxpttvQ2DibFlYkNUJu9TfyIHyLw/SWMuH4IH1FR7Dcw7Pf5qoP", - "jHcn4/WH9vAcd+Nc7pnXhgjonWlYqlBp0BPQ4h9CPD00orxn0Yie22fdzci7zTTwHiwhwm7kZvJrcmTb", - "yeZehDYjyMHDZcZT5KZLVH9K097TrXme8dytoNo4PlRg3j9kSJDgIAoubsaaZVw7ZNhCOdzUosI0c9Wq", - "GSTcxBFGOwpzRUIW/aMONcSn04HBl1wHz/p/f3aZkPM3F5dxAVcoY8eB/cTPbKqyFYgWN8qj83eX1SUt", - "cZuj15QLOhWsQ5Th1uL4iqXrqYBY6ymbKZ/MKPSCY6jz9TaADWDUJbsnqZ2QUvJ/lqwZod945jlI6LtL", - "6ColbIuF1QxngyH0E96mUNKwPaQ3diCapQwSLvtr4nO36IbpsmoI6O8Oxb8ZYLcE3h0BK0PUML4S/jrK", - "QAMKB22ghzaA8PoY6sD6ydyzPuCwM3pI/iRaaFyzU0i7NvMpzcirF6+eYcqej6oS+JU1dYI+ss4rOCrI", - "jm3aTM7zLh5dbToMWIEKBaeDzKOFzUUSUn66jpBh/3BX/M1LIkgdZWMl0LyZ2Z81tiKpylhH1kNo0GFv", - "iI7VyHbx5seEvFaWPFelzI5vKzD9TmpC3CoZz+mcnWpqFlsspwWds6+cSiozppmu3OlS7EeOqCTvB0+X", - "CbmQtPi/3g+CU8ExWS4wsWNttAmduTVMzBwUVk6SCicMyduQdTyUO/Az+BV4HStpxBB4xzl3tlXKIeO6", - "TxB7RyFLyWRETkNEpU8jGZY2ccNPSODYTnz7+nl9jaVugLtK5/WTOEjmTskMXsoeNx5QKkdPZL9Huy2Z", - "surcNk0eHzzoG4j/cRNiQapQOkcHGCX9Vcp2k/9uPtWd1cqtYscBnKn8FLNivFQ06/G+c/bmVatDSATq", - "4O0GHGXViDAWqPI9E3/eF51HN3Ug+O0En6l87BOkwNPIg9N+9ynd95NIVowruEU4BXqk5SHZIEEHG1+H", - "RZLgXEOtz9S2QQIzB4+EaCao5ddwxOvyGF3Kjtw9FU4Nsjwej8g7w8jEGsy+tmy790Siedbg397ZTk3k", - "JUSe9E2ygHEqHUkWvvZg8Zd0YGkQB1W7ElimrxmkSwsjLfgM7FS14fCam5IKB50pF9yuRuQZTRetDui5", - "h3a6r4d+Vrdp/fGYysEnoR8PaYc2PTD/8NjscGR35uoyLz1xtnDr6PTlxbFH7Soc9ZxpAIBMGbnkORNc", - "MvL0/MXHFWLr2zvIr3645wD2kTHvQd6WvItlpFrbWjhoC6GZtHq14Rd65AslPAYx02LHpGAa0kAfR4NH", - "m1AdZ8xSLsz+0bKBnBqAI9RazaelZWYH5cGWNmlvQbOxZqlTV6Ag5HaUbgHJZ1NKWYZeD5CqEQYJTw7g", - "I5cQdpOKEtyYuOcPpy8v4igP6kIkwLY5r0mVDsYeuAW7szqCwvIOEsFD/uXFcVz0b+Cktzbtmf05ZIKC", - "7+uiFS0QVcmmo7cjHqvDHT28mt5j2Lo7fHk9nmltw34tdSBxDyUoLXaKi5fuGmUs8WrerBTknHJ3zXl5", - "ev5blRd+Xwc5sUNOpMVDi4fmSdyzWBBpcUs27HG6RmnE6LuyYZ90Kcp9eFYPH+j/5el5nXCTz8IjSGcC", - "+nGc2bibF8ZAbI7bKyuCVFk3yzx784q4BhGu2ZgnbqNGQ07Hst/Cj30X/sQLbMgKM8QnCZ8AqQoNu+Q5", - "l/PhUyHUcohP+PEsEPwD606PSjWjHQvC/FPE/LOkbXlQj73L/aU5Irjoui0Qpck1z5gKP3Vkc39Yoddc", - "muNh3gx3/3IPJoopZ7cWerslnaK7b/n1zX3dkCdC91/DhFet/SDOdogzRR/8ot06i9+4cQ50zBqdPxXT", - "XF0iuB/FNiugYGKNDfoFfuHHdfRLTqnWnEFtkKoQwAxraXIJXGsKqfQt8eUwfHm1ULajaYlbL1jzcbnD", - "GrQOPGI7j6gP64E5Rexc9nvRu51UlwHLscW+1YxesyXZXtGIUGP4XPoQIyCJHUWNCqqdWty9n3NosLkl", - "qGTia2M3y/g88cFJuIJIQSPTkZB632pF91aT6OO+rNY4YNW91QVCr8iG5lVjUW9S2P7eEko6w0Nwx0Nc", - "VRZpzcBOFvSakamyC5RzlR+RaeNO68mleoHmhjSGx5cYKJMC/sPkhcxY4bRhLJjQjDl8QigxXM4FI64F", - "Jk1A36hMMSxUOQVZye3H9PE4PNPsKw8+0lPNJZ2+KZjc8ugo2bJScCydusuh5yfgKAGdUbdBdjAKsaGX", - "Cr8A3Ae8xn7mGN2ITXBlp61UYNzU0aU+UbFbQijNZ1Qrl+iuSFKvL7VjSBuKU0UVgH5Or4zFl47IqZKm", - "zJl291AMn13T06C2VahntICUSxbyEHLrdDUKlnxOxV6xqPellbVP+aCUbSdCS6djxOuPSny30MlglXHN", - "6bLLw8rRMAQ7edIFYlCSYZSKXO2rZMTduYK8k2wpVtVUdPogmoflVkTMPxgVJTzvcW0qrRQYSnwxUTUm", - "DNUwnXWPcW9eYIJah8NPC35Khejk0I7p4JHmVIIJsul3+pdXRFPM2ragkmSaXwdlwzdJyILKrBFnjLXx", - "hmjPHNKC+3TPJ5C9RgP7E3zG0lUqWAI1zH05PlCH/O0dF+PrN1UJ41yLRtXqGZ/796ERuVwwAwZPkitj", - "xYoUHgBDLrMyrTLuFVpB2XRDr1lCNINK4r5qyHFrs3TuOAUWgzC9ma6f9c6MN3J8B9bbzXo9uMa04GOH", - "0g/JfLuOZv9s00B8rVTTGxup8kyTV6HIqJJidUJoheFIw2kwAyl3z/TXD3/hAM3HgnUcCs6TSaoyNvEn", - "jMX28TclyaSaOob0t81ujVxC93rEcfPhJDGZ8QrSV2dYPfsr0CK1vw9JmrOwn5Bl3n0HjvPVEnzJ1HNk", - "Nc9uWOq0P8i9+TawqMn+8SfuQCPxJ9Xz2yZjDK1znmWCLelDRllsi4JowbsRC9EzH9i5VjerZ1orvcXI", - "SaW7khau6VDQlQMMhjsQNfXFXtuJKkakdYd2vzghoAjDlNwLZewQxsOTdmpymObbmxufnwKdshfKNGKP", - "AqVAFMNfh1h/dgi7GD7D4uwYBTIiL9VyeK1EmTOQPaEnhaCmDIFNXlgTLNfgdzNsxle4z07whxdT7lNK", - "+jYGZW/IluMFkdcRMJzSuKtAfoKwC1DDKqgQkoHwgyzjdXSGU6zCAKkSvr4Or2rdeo7ADVyZJFGFHXJJ", - "NJtxic9mIWKLLteqM7eFJMnYjJbCDt1+heMTck4Mn0sqPnLSnDU0PEjLbmnpIDUGAnlIORk5j/t+ZVIZ", - "i9bLrpgMcgHX7mQbvet2oGSzdmAnV2lzrQ7eVBUtzjn0hJQKYKsDamYOJ05IxoyFsEQlx2BhY1niWlzz", - "jOnxVND0SnBjW9+WUjOaLhzdN77VDEveJT7UcWyFgQcrVUKkvzv4Ulb5BKCkv9XcdRmDQG3/WhbIfeoh", - "qm8yacaeGTW+becAHpGnbYgixwE/gcp44q4Fc+lIBDI4+Fg2KJ9wJdWyGbjWGsuxK7ocAw9u1eiOQBP5", - "0AY4m1834Nn8OgDUickNiGLDNkgHEC/aCVN3c1sDavOrBlSbX6+lVk4GHjab5JsMboYOEMNrqkH1cRA5", - "qyHyfQWQc7/F71vwCN++a4EjfPu2hgY6pVwKc1ntAkjqXWuvbytQvHWQaP/4zm+vHiF8cybN8woK4cvT", - "ZrbnMB0Q8LsAjf3Cm30Suyo8FxQNGCmu/Hlsi1TPo+6K/4Fl2/UJRNy1CHGYNAWvpArlQfHA4p1ZQtIF", - "1TS1TBuiSgv16f9Ohx8eD/80/hlvsO56C0QyToi7yStjyXe/b3REAsnpzUsm53YxOPnu98mgoNYy7db/", - "v6vx/te/PXTY9bZQ8nsLtN5xkntp1FU+UJQCDQlw9O3jb473VbYBg6olbPUgeMsK4R37YnldwKMJ8v9q", - "nkMx6YyU0t+52A1LUdtUpS1KS7hlOSgMjQ5YkDGnRcExTyqw5h0y2a0K4szD0kKR7p29LtlNcFSEt1SU", - "5avXNHdzWm+QU5L1VxA2h+3drbWHdbC3ftzPruq0wfH0u9/Du/f2hLFryRMaBAkH8Shq71yvk5rH7fzr", - "ahxqcPV8Sb3QLUqcg4VPUrSnfSOkNrLK4yLzXA7vcuF5+e2z85ebt4Qol/0Pek0x6QFhjpOGLJ3UXbKM", - "E2DSktdOHfgH3HPAo+e9vKwe1accXjVNaF9XZSJPz19Utkd2w61J4Gu3OsLNe+nzzHs+478mlulAeCEN", - "L8T3QjsUaiNyXi8OBPJ7CQnt+TULz0DDuVBTKgiFxPlKmxOSCmXwmhfC4ky4KBNBLdPDlAnxXqIDA/pZ", - "kuelxIT8GUsFxbu7IaVh9etxmAJKrSY+wwSX8/fS/QyDtuduTfC9pimDep15KSwfhnmUJpNrqifvJSTm", - "z/GayCzFw+bavysPq59h61qJ4UyoJTEsp9Ly1IxwHMLle9lwhmCmSvqPY/sDNE/ILOwZk/67hkN8pBYq", - "pcKQTL2XUtkGdCYjdw6Y99Wdq1Pjn5BJ+H1UaGWVI4iRVRdATUfHE5LT1XvJbuCdD+u6SqY9Cjqqdecn", - "ODUj8pLd8JQKb3lyhx1mQSx5LwWXV+ES7gtFBGWkcXQo0ik8FmCtb42+WhUOjt7Lp6Gj8LPWBZA0EYxe", - "M29igBE88V2e/e1J82Tfy5Q6IDU6E26hfqXlacMthedOPQ+bQl3wCWBYtpI05+l7OcE2R8eTEXl2A9XN", - "3V58ShPXEZV+W2rpIFJalbtZqBCrEXnjhZV5L1WBnOYJ8o2crhyFZmXqRqgtm2D8nDg1aLTU3LKj0Wh0", - "PCFKv5f4rbtjATvHXxJYbqqkUYJ5hdAAZacqn4KTL5iLcpYuqOQmd8rTK6wjx/LCrhpqG55GSJNGLhcM", - "M2yHfFZw58en6D+SV/z75L0EevYMByFo3OFD4quQkDyjLFey0qrUdUj1D99/ZXAwx5SgwRCc/GCuyn88", - "cKaggXls6VC6mNcvwJLTwc8vA6/DVAulBj8nN27ifS2pr60B74F+n7Ji1euWyab5A1nl2LC0tY7vHicb", - "tt8bnpd5Q7sJBROCRYScYW8H/u8ej1DrdX0GJ797/NgJQImfvt6ptoEU2ikbDSx1T9EY8hI2pSCiebWz", - "iFDs0gQ9oRDlVUJIq+S98ZGePOFU1opqlidEeSur0h69AbOlCl0xqg67D5IBqJE9rWlN/bV2+ada05X7", - "HLyh+ljZ2I0ldYhBvbJQ2dEJ3HWzW6XIo+mtIe0w6jx5/Pjx0O2HpLQg4HKr6SqMPi1nM4DmlNklc4gc", - "YGaiSNz3YWQTdZvvIVH7HVrqNi2EcBPJmTHg99XU+Ss7QYTaCxG/x717cfZNuMGtApDYDU0tSt7hQgkA", - "HiBrUJYgOWjwnnQC0i9hvfixY9OoUYDvNy/zRj3kJ+6qKudOPqCApNIzSOAsCWE3heApt6HeTxuIYJhS", - "gYqcAtbJ6goxrpW27VZdVPLCjRZIxwMPrby17gdEVacffS+PmhY3q1IloNBqWeAzlXKKYr3YRw5vy/nC", - "EnaTMmhz7PRWeAO4sZUcEPQDR08+jUIDuC2M09AU/BpbEGigqbE0vYpYMNzXUCypNyb5Ok8RZF8w/0DA", - "mjzNaXCN0lCR1a07c/oJaqTdwYybF8H97mwO/SSL+G6ASkH+TGr9wkm7zInYPwclYiTU/BGXM/UoY9Ny", - "Dg2Y1o0GS6rlI6Ti2kwJo4ElIEPjF3aLPoOE1Gg7b4PQsO9lMOw6We+2Ad0Lpq95ujPnGXptZGAa5ymD", - "mxQYJdhNAbqiU/FewAGhxDJWFcY/yOIrpNLDOpGyWZQ2U0t5TDIFamNQABtOeP/zX/+NGlk9C97gUAVB", - "KhWBlQzn/JoNy8LXXscbUqb6PlWhp8pdX6oi0Dy8VnW+Vnlk+gipy7rO5RaOHWAFaHl2rG2j9ut4dsMt", - "8kmHsPh0Wt2T8MJZ6W2lzJgWIJ3bnlK6qpzpiRrulkAXwRfCESSKM7tC60blhkWKBfXXWm+/8HkCCJeo", - "yh+BVlgZ748xoPjFmb8J4qNNjIpg5FiF8h5Tj8gEiLYsJiRnVJrwcgYbz0Aooxcwh/Sd7nqMvr+ULBgV", - "drEK792owozIxH8OA1JSaHbNVWnEqurTmqHNvCZzes3G8QWFk6jqMvoMcP52FEpBwilbVFutdmf5xKnb", - "oTxqF6JgmdQZb6aaCMeKdiyj3G22UevQVI76FS0hOJ1IRTgMkoHfUZSpFVFHlxdnGxn3EAQj8nRapxKP", - "wcZNRspis3ZsFEzorCSUdF2rSo4UjWfnL8460ol6AEqax1+H55rm3qLS3kaAp/cQSr2mOknIJC+tZdr9", - "teHHM+lTsbe5psRTxTZWBILmjcp/5J3Om05BfMlleeNdi8ibN6+GV1wIKLJbWybr7KESHo/cjv/yakRQ", - "cmT4zD15lLHrR1e5mU+Ch71DMyprcoCh1/w8g9DIWa70qjpQDE4Jty0f7VnlQjTl1I8ZjFOB3ZmycIAy", - "/ZOI3pNE3gD3QSB3C2QA1lipfOxQ4iEFcvxY9pfHbp1r4ri9iUocx8wuxmrKYxT406JNCyzlGZr7AimO", - "yEQqyYK48Ib+DWp5QiY5y9OGWErnWpVFaOmNe9SQBbdPyCQtSsPshDyCfkqvxoUSPF1hqMrrd6+ePsIv", - "hpnm10wC7dbsWUm/ZEOUyIIv17ejxz7aOuNY85u5WUF/sLpMMQ3wRKkctnYyIYLLNd8Lt1nIq5ynTrbg", - "OvGLepVRas1ZPp5pxsZX001AP9eMEe8m7kHCJfmRf0+8O00z9YZbXEIypqH2QOV/OXGjn7wOXq/eFI5w", - "+MqQVywfvpAzRbIyL0bkqTElmO/J72EedPnhH9iInAXPlZCfW7NUUJ6DuSN1CogB7YsSk1MhvLuKv6IL", - "qucMTm1slaVifDWFtwVirMNRd/wIcdysO3I3FSh+ZEF1Bm8WBopP+9P0bCQgYfPsKNZagZVVGzS+VmzT", - "QtAg9+bSIozL/XLno3gN8DTk7dNXiEV3OI6HgcIuzccLw6D4xMfAHzsUkVOV5/HRCG04QrXF7VFOb8jX", - "34Jrh0kasqLVrMN52Zjokb5lBu4FxDCLwia+Kn/MR6aEdVOp5FAbgzEc+Bfotouc5e7j8Yhceg9TUAUX", - "KwMvVB5rmuqhQ/PSgHIXR6I4gK0qxpaaKxPD04LUSgbY7nCXQ8PsEHbpp8pV098VMdYg7N2QGAm7pi21", - "UHVy6ZaAN4wJ8XG2z8CWvgUpfTiVa3tK4TJALfkWksmAc7IiU1VCgDBKLUB2QNZ9TPAtCerWCRROb17g", - "GN9uWuWt5vM50+NdBODbNa6ifUgRhQmVmeNkk9PzdyfktdPk3T+OIE4qX6latkTOPayxN4FViAZve1QI", - "hbUmKuNqo8yVX7dVhMtrdYUKc61bj8ibmfXXG3xTN2TSXMmEHDWG8UTUcE9i+hhScqRUkozPZkzX9yXf", - "KcVl+p8dTK95ank+Iq/60H/U/L1enbgJO+R3FYvoq5IBQu2njT2tQsyDtwVkT9pFVSAFNnSz/ud+F765", - "ixK2yoD+TLctRTe5Ug9HMzxEf6K7z7IRCLktMrNZwAXDId2VrXpShGeiUDulFXycDDjEVw+SwVLpK6YH", - "yWBK0yun2sps7NuEq3HVxCyoZln9GSrDRXXGsI8QIHiKlwvOzClEB94qJMvXs6ijDkNQhmHWgvuQu0CE", - "MMTOawMtbLrY3+N9fS8rv5PN4j6nOAMxSlyzYDchqrSpyhmW+vHvaz7f7EOtQ3CGGQdpuniUMQupviv7", - "XvDNdAiFTu9OSQALzjVny7BOozAK96EW6f09/OPukVDzhCyplgk6dB7DqjYf4io4Wq3EHdaHA3Qu7+nc", - "aSb+ruZDTgmdUy6NbQXk/s9//TdkGyjBBQxHxbfVhJwLulpqqLXddiVIGuXtTUJSwYupcgIYvcGSZlxt", - "PajKcyqzUPQw+KBUx+jrS1qm6UNj2DtM5FtNFSyiR6ng6ZVJyBVbZWopDWxUCcfHf0mquKKHW1iznP6j", - "yt0gFM8ZYcKg+UPi9Tnk+auILQCmmSQGS2yuJet/eXqOQKoClh+SUQlhmjHm3gK5EVrernl0VMWLN6PE", - "kyBue8WFH4/Iq3g4+BOiZjMn/b23EdYghSg4gEtNMQ95ekxzlfGUTKlh3/1+GBzAHGN0178m3Y7ID3y+", - "IBiNuHP1aBV9uJV/XycIwFeThJgyXThdWJV2qGZDf8sDwxNWEcPH4WEwsqPR3THYX7aoKB0r2k+wnzZx", - "Au3aQQ1tinmcIZIMF+tabfN04KZVCcurDNmIvJCkWc6VGCZQIkO0NZzYCfpgocsNN94+deTl5nKhwKyE", - "gx/XPp31lw4ZvMXJzeUnN+i8418A00o1Ao3TKu+z6Nb39PL0h0bB2a7VGJ8UisrgCAmnRSb/+mVyDHGk", - "RKqhKp60F4d+wBiCHdwG/WvcpfI1w4jSJOMGHYbqrtec4uoSslIlyUusCZ7BEoJL0MRtZOJGmMDhT+Ku", - "L72QLCvakno/NLtoKohg50qzYuwFafWO5344Y9eXSgnjNSI09kS0SMxUz7Kx91ONRMPjD+5AATMc+QVl", - "vT09BlGNyJtwDxfc2OpgG6cq2TG6zwELYtdMr4gpC2+PwpWMyDO3tCrpUoOOvJoc0hRIEjYRVArXAa2d", - "mgkoZOdTNQUWXkp0B8sSwt3lKC9E5fsLL4K+nuw7Ay+/VoEbIEYI8vmCGRuCtj3YQG2avJBFaUcZNwW1", - "6eKVKn3NUHe71PiwuChzKiGQKy117SbvcAuSb3rSuqjY8sRbZqln1Sk16JJSOeKWWgZnujYpO6rd36AD", - "KApgxJPfdKzshei3Qe/LmlVG+GkVtn+xyeEaSBfO+IqtYrgHKwb0a4bCBylnmD0hwmm4SwZ6bpW5gArv", - "OWF85QUlElJffxIStAd/LzsekZ+wJsTEr2iS1O4SDWbp+I5jmF4GnADbhFeWwOOfECpX+O4efGndxmcz", - "dAh3CnNjvCN/10lCVH0Cd+qkqd8eJ2RiGigGyciCAoNPPBHxD9xxyhzIMShBjcjTenv+0EIlJ1yw3xVJ", - "BaMaWZONnzJuZpIrya3SjbqPkL2tTjGA2sAxPt3begzH2RdMsydQc0OopWm7/oMJDHw4aBNkbWkaeRP2", - "2+ub+qnTUvBLMmA3jsXtO9Iz6BVG6Ul8txYxm5oMelmGi6BXYcgFzRmZ+OOd1AE2kE5OrrDaftU+IYUo", - "TdvA0SDWzTth+0IfxFfsyT4r9j6adSHsjuaggX0ZGliLmvYjjedKLynmF1Oz6vwb/MwqXLWP1mqkCdhI", - "iwppGhpYdEKQOXjOTkopMFIQIQCxO8hJHY9LQEFBoz7CyXVtzAdDe0r2KIqIczQrQS2CmO5jSMziRYof", - "BEPbfMZu/51ViH7NB4nQrYF0ThNxDZ9gzCTiXGMGRLMmcgdU8QpdpZbFCF1ZsTelv7l8eb4/+9zotR+a", - "uO6PwHDjwRe43i3ufREk48A9HSLWem191MTN3sa8Ny1JPiI/UFRxZzNH2UdhkZauDOHSKQjXUCIag3R2", - "4lZvSgxhGUHCsGch6mQfGvQ5neA5Z+oTdQU3unpZoCF77wnjfU7QD9TnDdk4Ch/lEvWB73AxrWYb49A5", - "LYwPC7JU20e1Tcn7yjxyrEG6K80jnyXwkbssCLoiTlF7UuWi9gPyEOPqI2AcxlPLfcnLxmPI2krgiaY5", - "UvRtw1hWbIn1qmHpGqJ1rQZe7e+pinGAP2Yd0Lb5BfzJ0C/HgdppQR4IBnMFuP2HhrzMxzNB5wbPx4Fo", - "t/dX2HM4wtiD1Kng6RXcyPoGm69pgaW1saJ6MCTBX/FBF5Vs0IIbcBJsZgfJAIznECWfZZChZErTK3SS", - "cxQdPSewQXckrLj0L2fQxtv1m4lq1FJClpeBHyY6wUKJbHzFVrHLv8ow24b72e3PtQ23WeA8MGrjhtkR", - "QFI//8syH6NZvRWK+fU6pb+GbH1gaeA584RVMP96GebdfA+92dzFX0mqwNBL63pSCLFCYYhGdKTV5kj/", - "eZuR1tD1ZuCG7kBSfDe5XYxRiOFZU+i9kK0fZSDWMa1za2x3rewK3Dn1j0RP68eZWzzSh6cmj7vNZA7U", - "kIJq0JYosnp/RXRLCU52qGt7Fv9e1qMUWF2pihOG+Dh4QAEFDns7IIDi6hr4vgXVNGc+p80zr14rWf2O", - "PVvZZ+D9OtyOfWKnuH8xkHLueMYuVWaTYf2SDDJN5/26n2k6X++dq2vWr/crdc3We4NXoGMTuzqfu4Y/", - "slWjLz6a7ep4Aa2a3Zgdo5lsZ1dmT6Fhs7dgbKfGeOEaeRRueCJv+sEHH4UNDGvJ4cb5tuCNI4+BmJqg", - "rEDTOtvWzsNGfu5I1TIOZL9tm05OXLIbW4EnFq8XpXLNqGVnXIPxZXU74ZlHs61UmkYWRieuITlSKXiA", - "wi4TApESf/j22+Mq8h5kwR++/RaUuEaKqcfDP/z8r98lv//l3+KFVewiElI4NUo4blMvwjUE+yBsfW2S", - "R6P/tdvxyc0UBWZprMp/YtNXp+dnbAZhiD1YZxuY6CvUWFJqx3+nw9nj4Z9+/tfX38U3f8Xlerejgs7Z", - "/59mxXG0BzC4nUyqsSHImgsCnubMFDSN69JWKbHPsJdKiZCYeAPSlTcSzuf3GRbvJ9t1Eq/CTvc4hFKL", - "sYclHkqn+pNz6V0hv47Y0ZubaY25a9EtsPR+hY2BNPlXaxXov3IBHTqSh62vw7z1jgn76iuubwt6fZGi", - "QTy/7IAqThID5xkTzLJzahe342o7GEpgIxlMc59sBBdeZZK+3epjLocvNsox1Bn1Q8rhakcJKmjGoBni", - "UdX0EdwBR+SpKBZUljnTPCVKk8WqWDA5Ij8tWGUTSlqvL5uzceNny9aZPR1+eDr82+Phn4Y//z//1q8A", - "6BneNXsaddaqhsNzULd2He7x2K6uf9pR6hUyK4w1tWz3kL41ca3dwD98IEc5pguSpRCEz+AhJGOWpeDv", - "fRyddMmzGL6uzwbNtq4/Ctp1dfNhbtdOR+q4WVc3arxiR8P1mKCr1qVzI/3PmWuyUQY/JGjxC3G3ah9k", - "SzEdtVXEKXuEClVVyrJQ27DKBvQ4diZbU7r4GikQKVNnu19fW3hJcZSrGULIrSWvomRNrpRd/BnfAuCZ", - "FN5Tw9uYu167PUypo2SAs4WyJHZBBCTzbOU4+vrx41aWo2+jG7uLScFtYS+LQpwRh/SV4ISgZuTvNwlZ", - "/dy8vxeUa1OdnV1oVc4XPsW5W8QcwjJeuXudvygSaolg1FjyDSkU93651UrXl9yMeaoiIr5p6gTfbO5m", - "6494li0cduca8WdET4Oh4FeMfM8+OICnJWSxD9gMJ7ykK9wI4dJYRiF1reCSUe8QWSjh7cjAt2E2sAia", - "ccH02LA5YBqSAyvGQGTjHBOx8blU7aLIrUQxjeatLX27J11WVVphXRsn+AJXsUkNO+lzY59tk9XjbptV", - "tSTALVxXwTQJ8PLRaMAmuhdIXuHyyNette7OLNapO1Q2977W77WBt9lYn6HhpvYSvre8nmuOxz3TebpL", - "L6bzfNTI7Amey/XYaFOCLxfUeIdl9/tX7n70VUK+8jk0v0JT0lf+yforck01d+LW24nyQrAT8n5Al5Rb", - "cLkYzZVVR18trC3MyaNHDNuMUpV/dfzEJygkjeZQBOro+Mn7QTy51KeWxS6CD3fKZdfpfr75rnav+czC", - "ojeNQhh4W6Wa9KE+sLgjrFd2jGwE8i7FcmJhnBqmfK5ClJobizzbQDan7sF8BELP0T5Wjq04gviXoHeF", - "UDR7VQrLC4oeMutuitWbUSTTCwr4uo0j3xKGxAyzqeXXFFJhVRK6T5jXcwxdhzweVhF4MvcQ9TN537xH", - "C5WzRxjhVb/xmUfvy8ePf5e6/vAXW7vCPB3+DbOij8bDn//1dfLNt9/GrTcfeDGGp7CNJf6NF4TqdBFy", - "/VIuvftGKQuaXrFGNrx61Uee1sm3j8kr/v0TtF2HYPOcSj5jxo7+YZQ8bmZwmHJJ9WrnxbVaro8Uix19", - "Qx96vI+RpIEIsWGfc8FeyJnaPGJuxhnX2/EcNCJwcqmsgXHVJVcZ1FaIIKNTDnNQcX38Zyj7XkExo5YN", - "oXfM7BaVZG5baBydcuvTmSXk/SDTyxs9dP+9H7gr9vvBUC+Heuj+ez+IBxvGcf17algrYw3UsAOXnU1I", - "9DaqhlvQJtvhH9h4urIsQs8XPlYRfh6Rx3BdCcvgzPQIUwwhpxRuio3JkoAHjTP0QO9CJ4xH7ciQ87wu", - "Lvh/2Hv/5TZyZE30VbDcG9HSHJKS3O7eGTs6TsiS3K1ty1ZI8vSeaTpEsAok0SoCNQWUZNqhE/ch7hPe", - "J9lAZgJVRVaRRUpq23PO/NEjS4XfiUQikfl9GLpSJCpuLn4cIjJE3F4Ot13L0NS2i7qZlNS/qhCAzDwV", - "5SeUo4uTw6uTTrfz28Up/P/xyZsT+OHi5O3h2Ulr7MFGE/hXpe/UUlhY/foe13MfBMbhEJVDofeCQp3I", - "EkCmDgSVLbAKeEA74Qmz/KNWegaB7FQNIq2VA2Uxsp5yy4cxtxyjbXU2g8NOq7DWYJW6roxEou/YDj6Q", - "Ypfw5ZTi+IbN8zDsskxMeBZDDBpEq2mW5qNEAoiVtH12xJNEZL3ilzQBEM737vKK7YXe79GfPARTwLvx", - "8UtASwIYNUYINlzoS/Bw3MlMMDPlqQDOIxkH+r8IOuOBDMp5jdKECfYoERFxJX9nPH6vD6gBq3sVeUXg", - "flwdflZhRO36RN3raOq6qSbi2puUq+PwsdiRL4V28GKlkJu7UYWXrsSqysCr5bZS69ousUSorkgKaZ1r", - "UC0LUfVtysKH5bJu+doWPw7fhhowdJ6Yy9ZUgN+WaAmL8ometCv9Rk982VJ4PsbHrKnhtPgeYgXq6oHX", - "+ra1/CrmdXXgA3UgfG9dHb7mX2IxX3Eib8X1rRR3LRf5jbwVf5fibmGli2par7evaXnRKeOgVNXaYZ5h", - "keNSicXapJKBTatVZadKEvPUYlUBxnqj+i58qTWVblzfcl3lVNY2VRXZTb4mSlW5lnGyjh2I+JlO40Qs", - "lnbaX6pJu2miet5gmeok+Qoz78BqUxP5rZbr8I+j7SrBr30tQPnugWnXU+lX8JOpdKyB3g2Q8N0Mtaro", - "WM8oCO0NFKnUiKneLVWCq+0NFFjQBZWqpnJs21fkvq5WE6VtSkdptZTmrXqvebXTRfJ7i9Jvw8cNdWww", - "j0VddXNp+egauexbVFVl9+90axiuNycQD3SFbQ7QRVbf7hIQ88Yg153uEnbkprCcj8UbtnjAt+QNW7Yq", - "WhasU0KbFi2rns3K1mjRzSoo1HnLcovS07ZYzY7coGi9WtyggkKXbFBoYa9uULKyOTbp5qKe3aSs17Kb", - "t1dWalst6DY11BvSmxcO9vPmRWts5ZaVNFhUm5VetmM3K79kGm5ZfAv10WA8tyxdObvaClzduddWuy/c", - "dzcpVrqztC+2eNtpWbL22rVh2S2bbvQrbFG+7OXYrHit16VlFbU2xba0HRhx+UYaC67mGrdsBpRS4xon", - "r1T45gCAVoj62W8LBhEeUmribYJNU0PQkujJIuAiT9OEHkNWpt0tPKToSXipteJjPZ1vQB+veZ6VM3ys", - "K3pUJhpr+yLTEP5QbrrOx3zGnT31pQI0Zzy7ecTwTFedyMBrHJeSThujNjcM1Wx6xnhbesHALnSRDIyj", - "I/fsHAioUysyBqET/SoD9TOKY/D/Pli3uI2PlQur2SqIoc0Dc3mEOIsipqHWirsej42wtcGCwJ9uiKUP", - "PqtOXbEdS8uF1GcLUVVdNhPcQC5xGR8QyTMgjARg1rIbfNmE8Bme26nOpMWQJ2rf+9tpibCCu8wJFgTS", - "jaUCItBWJAH1D3zFhNQum86NOKe8vHKMenVt2iYM+nSc7RMFm2ponSC4lJe1IXH044WbQqLSAwNNY2ks", - "V5GoRB/98NThpa7PG4WXPjzmkh50iwBL9yNXdmEW699414lnEb/qJYxZvZWYtq1pI3HdPr8iFsZer8va", - "KsES+GCDdWkW3Y7JonUVI3VI6zoXg558A93SKOpm6N1N69yZakd/FgoO7ne/BhbSZeNK36yV2lMVO9tM", - "GB/W1V8f0qVvasdyzm00pRyL7Va8KcniuDm5IiiKZ8/3N0+1OG5Mseiz03FhBeWGEEsI/a3gKcMiXitm", - "AsSHbCAKafhxv/v9fvfZD92D/Q/1XYSppbePdes1phDsTIyBdx3gJuQngSo4oB4jlbc3+QDvBV7AEd6j", - "XtMQbkWB3rBsfxat43EegsuQbqsYvw+HsZoJ5awJJi3jMU8xe1OJO891UsShgkzAXE4Fj8d50kUsNP+b", - "pEE8G3NbjhtzWoLYfP9sv12Gy2JW83Yn75rsE3/q+mMLgePnBlNOFs7isoi65d7v4rc8E8wC4cP6APcV", - "B2lIz52tO1FvxBw5Y5hxk0MnevsDtr59H9bpajfz2Ugn0Dg01GcnPJoyoFY2U50nMRsJxkvflvAkR3P2", - "MdZW62SgdowQ7P8cHMBY5jN3h6FEQrPbZxTFbUIo5KBzAbG9g06XDTrgDsUfj2yW4E+HCf3q9Q+DTn+A", - "cYwY3g/87JA84jrIE6NdL5EEHtbZUHYz1vdv1gfxwb+gtX+74iOo1lkXrt9I+e92RJqryObBSqtW66Fy", - "qa6ZVLmp1J4m7hdA759IKzKe0AUQlITrKo8iAUB/AOjGeCI5sg5RDb1BBzH4shmY8XAvrLa3AD65RhAW", - "ThmQitpzBuGZTz6K6NESDHiADjVz5fSfAsLG5SOVZ5NqrsrvNUjwWBPPJvlMLOYIrd0N3FxnWrfgy7/I", - "qwyIgLvoirI0k7cyERPRoC65uc4J0nB1lXDTlsDBDTdSlSdw6vmzaRmrxof9LAX7wUR7HDgzFUkA+4Mz", - "LFe1N97org4cS2dA01h4unZ4OThxl2qkcC+ChFV1A1hvKwp12yxen+uSDGnNPt8vLtiJupWZVnBhCskj", - "wL8nbDAhlrk+CslfSgDZLOejeQGbUztwOdduwwfldfDypgsLVhC9dza6x56E8TddYut5VMRHaa/rE4nO", - "PZOMJ9JtoAWFNI/r0Y/P62NyS/jrROA9ysfjBl8Ppnm0rUzntrmy++bV+1UWgCsbQlEjjTBIrwo+wZL0", - "VpcMsXMrSq1zdXJx1lldbzkymD7/9fTNm063c/r2qtPt/PL+fH1AMLW9QogvwITe9jRBri92fvUfvRHm", - "XDROQ0SIBwt+QHFX8IhHOslnyqxL+Ot2Mn23ri73yYaZg1BrFzu6YsYuU36nyhPWCn+i5ui+7y7644g7", - "S1xbO19/Ch7S14yz1Ig81r0w+p3zq//YXVSseCOBgyhEOd4KPJEajsv6RTsFEvlkaeEIlbM0CPCELuab", - "brCkSy25z7Zv5r4W1KO6rlvo89PSaxMfOYXEmXG1rdoPtcxX7y7DYjUxMHtusbrilyK7FVmPG7fvRUyU", - "5HCDqTlkg+c5z2Vcr4jhEfKa2/pHJmS/XeKjpmIbvDM1bjXLbW42hXEuofDmBk/ZZq2U5tdpVDO+E2Pl", - "DFIfjs7fsxwe41KRRUJZPimfggoSn9ccowUPvaxyp005nq04XetsFCQQbUgeKnrs6Rg9GyT2PuQVNZzg", - "tW6i82JNbSVZpeA4x+7Xn0XNCxtLtd2hc8wtd5rsLpPouF0QPcwElcBHsGw+cctbGRZxuZX1FOSh3g9r", - "x/wge9F1h1A3jKtueYT0ytQkJEWaPnzgH6X6nbauIBpKJniRGLaJ7XR5Elg3M5FmwjgNVfBc+hRenS0x", - "Lz10NcMzYCEsQNpRe/Wpf+R/U+3SUgaX2wq1+CutVENQpFi5NGwABQedpi3r+l9zCqADnzKndIkIPZrm", - "6qaKYQsZ1SFPu+UmxtQnWP+H+SFGOp6TSweq9ADsOAGKdvdiNlh/JXt9XapdAd4ffHvgp4hvpdHZ/AXx", - "bNwofedbJ6xNeACGY5XhsboATl95/02QLguxP0wJYb7PThHfXCVzfMl3DeYKG4wA3wukyXSdGKDPGDCQ", - "UcdUicA9pV9Bw9YNpJAl0riCW69ERVYhLgxkVhVOrpAVVeQ51IMKEQBbE5MJziPt9v6DCf/XZFKWjJ31", - "+roR9BFjHURWn0k9lgpS/tpYREWwgS/VZA+tdS2hqbf8axMiM0p/r8DMtLbfFkIjtu5sHUphuZ91c15E", - "cF6IyeaokE1PZ78QjY0PMpmQP2QFOlnDY8pv8IiySUUtAyuwru/czSztJWLsDoJMiQeFWmxQZ+1rtp+F", - "rp/YdUu2zaNQFhZ6DSpuVTBqT6Mqdu6mD+2J5dcfV79N/aIz+UkrQGaFthif6VzZPsMIG3eHht8bBhAt", - "XabEhFd+79ah/hDHHqzBZvu763HUov1Y36ma5vO0vvGHBJME9N72/v11u4Jb4isoIIarTW2+KTausnWE", - "xxLu8oZaS8axUGvAZzASpXjmo0JrwxTou4Zuv5aJOBfZTELIotmu/5NM52m9Dw7+RCgMGfu54sjYFO6j", - "BhD5x+fPdzfDP9Z3qu7Jx/UV/gSPPL6/7xv62wYaAlEK0mJu8UUaHz+J32VLNNQVUB1lIO8NSbl5bkQZ", - "CgrZO1MRub0fh2eEDd8hyo/5gOBd9wxRBt2qxL3tr92U5cZrJ8SZMK/Nb9xGjwpwG7DAwTMAsPz1sFlu", - "48pbsd6FG3Y71cdC2WTeIhypMbgKZuCBUdhAL18fPHRR2Lb+I7fE49Tt2FuRZTIGJi64NtEM7JbX/Nn+", - "On9wrXfU392W/JpwVVqIxaaQaXeHxPhOWUIGQ462IjScCRUTGuSOsTrtUiS5O1CR5BnBeJHLnCeJvnOl", - "ZoDbBWQOylM6hTrNowEBlzyqG0WXz/hHvxdP1SXuvebn06Lp8vOhD39dvbAr13LGPwKckPwkTtXZq+Ye", - "QCKHIRCks1cthWkRl/WgIRzOje4wj6Vevy+PiCCTu88R29bIWLBbGQvdZxe4B03ZO+BMJH4rIGAESlEc", - "pZOX8zwx4pB+G90IW6at2nGVAE4OA+axkbbTEmvVLkkLhohVw9ilwR71tGrUFzW6QacPVQ06i4SrZ/1M", - "ns5mIpbcimTO3MYK5K2TjEdinCfMTHPrthmhBM0gKBEcnsClFuksyyEaB4YKMlL/WPWAtBHc8n8Oqrdr", - "K30UVO8CLkjdikSnm0bSXgF4MhZl4dHIamcDlJAO2QLUUQ2Zm3eXriTiqAJOAcnJPxtfHHozrbTVSkYh", - "tI7hU0vRUx5l2hjiWx4LCPqgVcZNiTTGEB30hhvbg5Z7p8cUO5pTntTl5Yn3ltIBIQ2CHKPfbSlFY4NH", - "ZTdG70/+sHINm/LKFpC2MO3kTmail4hbkZCbDdChisg1QuGilQunG2gjj9RFWFvF6PvsMBtJm/HMA2aR", - "5Y0k84S+VWBNOQUZY2V99lpnARxsPSRYtw7LC3ossh6481BsWKwjCCUD2l1k3ib/4F8IJGtv4TfHUG8p", - "vLHLlpHAaimK2jqRvxVXbLGa//vy3dvgia1bqkQamuLV4GiIFYnvN4tLV6WZqlsUXFM39w91Bme5cstR", - "+wZuvcDRyRzeVfAZCEis7jhEDGAlbgBj4tAG6yORM9mQk2JrDKj3Sn5kISsSLztONS0ADhcTRZYiKKy7", - "0unRKh/sz3KFh7W/9E/DWzzCNzFeL0eXpmkiG3zVv/Ek6UXAzuqz8MipU5rMKm+6W1+qEhOyrAcZr9CJ", - "lmm020csdIl1cmP25cC5HGdgA1zj7qujppjlhKKuihdbiHIrAAd5APRjMwlmy0hEcKN3Nx2RJGwkppI4", - "4NCBYnJnjvmD0xdH9V6dQHRXuCsMy6RxGzrSOYQUcHoCoxNTGjYS9IIL+cVszA0kpk65omcs/CATPH4J", - "EJCCx0gsh7V5xvQpd58KxRJtwN6643PD6JHYHVdwdBgk5ERibCbtS8ZH/gNO37hCMbfeVwl7vktCQ8te", - "ZvT9JDLdX3nQ12Pkb2e/kImScGOXTCt2rAX2D3iISytVszab9ngpxBXEEcdRqwEWqNM29q0/jHPkRsyN", - "zfSNk8IanoDaoK/6ddoqjdHHKRf98GmcpXRGd558FDGDwfYHqqLqs1ywHS9jM5/Auhd7xpjdPrtEtvKQ", - "/zNQlLDhFLlrC4xXrpj2vo9Se5WZYjvwu5/23bxQluVuf6BK3BXAfulmbZ7iWX+ns7hnIIMBQgMokj6M", - "XCqb8Z77Chs0A+U0heII4AoWDv45dXrHoG2KfcNz1vVlxdLVMih3G+g8nSjCvAIfIR7pUw1ZJsik2QDA", - "q6/dhonEalk8F1kvmnJnsTnlNU81k+oPYrPPuBUvnZa1/Eag5QvWDhiVMGcjHt0AFVshBGy/z96pZE4H", - "kambAbZjZCKUTeaVeRqo4jOQjV2cquDz2O8f1Eq9j0ZrS2WKpGJIMHaldXIZjLKn5uVbxZW3muuueRyn", - "6lYj3AaRZW84EB+h4617ndtIz8Q1BWrUmt4ytHnd8E7fmmGECD5WEY0sjnMrSAaAqAvkmVuMCIO06gxh", - "m+UGcbwnouf5YCmoy0MnGwTdd+YDOGW5u2xbZ7HyJJmzGU9kJHVufBAfRSfmNXYSUIhINbk2+YjeXMim", - "4Exp1eO51fAny2IRJTxDmwqeAQETRafOznKmU27h2MWvbZXq2ykYoGKDC5mKBBJmINRft64X60Pzq7Mc", - "hrh60W+2hD+AmWwuU3caOm2AK9DF+xDekw7YmXxFOa9wqzQikzyRn4Jdvz53p0TeUjhx1z8JuGPxGszt", - "z2Wwk4Nnf90M7CTU06V5aZ7zK6Ls3GCq211ri+pLl9oW/KC1NJaVmVl8MuH/BJ4Wd3NwNuhYZ8yJ3g3a", - "xtIw8ZFHFtOnMzGRxtIVjZ1ad6HQM8gKveWJpDNcWhPcMqzkSvBxlfAYA5QhcYvrZ7EYMP7gF1i9KIdK", - "aYspn5s6PbVOCs00ElN+KzUkgcLrT3gh+v3s6JyBJUbvobhZP+x4UqOZjkVCDElppq2OdNKXeq/y+d6z", - "/Wc/9A4Oes9+2EN3xB7Qcv5P999dNKegNRwaWxhbyY1VNHwnRjMeTaUSieCZkmrSn0g7zUeudffHKN37", - "n7GMbCzGPdcOLyp0dsTVVDhLV3pk+jxobczdRKx6ocDLDz4z9NzV+cwKBVsfguaumHD9ccr9F6kaPosF", - "8gLIW9H8kYzFDA+K5m90KtRvOkvi5k/cNdSZZc1fhAkhwMumL+9XiujrjJCPNn1zrU3tOPPXSTgjpYoy", - "weFaXvcWKmYjAXhVUF85asd7Sxb27Pr8nDxLmhk0sZn3F2/QFoYrd8aR7q3k2lqtCcLYsbHV+79CALzZ", - "5mczKsvGOkn0nSm2O/y5bss9yV6/AG0rMhE3trvRVs9CfdBgf6DeormTApKc6wq8rrkVYWWy45chAhm+", - "oUVh0tbu+KrmbXdYldX1YnJwrQVaEDGvMVqWZMTjjTUYrVvWCihr6y14Aucqj686mtVSvdXtC9eu3Vos", - "XfTuu7jvtiHSRgV3v4QCcFLVPuSt87KJEf1aJ11nOwCNb3DQY8jqrUhQZEuF0B1fDT6+9+Dffm0aXNDu", - "z97pVm2hIdBrIq5Xqjun6Grr20j9dTuWjx6o6wWzfPQo+v1OqljfPbg7vl2s7hF6trDBim6G6atIQWn5", - "vGSv3nJ/DoN76TaxPWv7b5m04iiR6UjzLN7uIrjas1bJqfPkXr7B9aZ8vZPpHhQ6plPQVu3QU/Ap+vMP", - "z0873c6tyAx2Z79/0N/31hxPZedF5/v+fv97oraCgex5qKa9ccInPmgvqonaOxPZRADsEnyJe1R8lAae", - "B7QSpsvyNOZWsIVKa8CebiVnJk9FBolDcRdPdSAyzZWVCcxc+PpY3IKMsUEHHljdAT7oAC5rIpWAjKAR", - "mAruJjLWmWfUhJARQiWDM9itIcZxxRC3aaOpb+U1jB+XQhj7Ssdz9GWB7Rre1shG+cPgmWvCObhggfrZ", - "XFB9fkg4h1azGUwr8fH9Puj0ejdSmxtEBOr1Ymmcpu5N0nzQ+bC7PRgOdqherIrv6DzwgHDQzrP9/ZoA", - "Y+g/rjdeeMLQaLEXeT7vu53nWFPdDg8t7r3ifk8i0/B9t/NDm3IAhq54QqWAmXQ249m886LzHuUydDHh", - "uYqmtAiu89TnTrfzsRdeNntFJEMRbeAqLuQ71YmMimDX5n2TG5EV1+WCURMIsjNpBIOq5qwI1Qt5eyMe", - "/gyMmt2BWruh2Ob7aaA23VBHIgNOcz8LbMYVn+Bd94aiVNQ4456sjuScBdbWS2Gd9jDdgQLWjx6QXos4", - "1IjjCPV7QYVz8uj4fM9Dg2q1CzfuUaKjGxEPFPgf/Fyu3fvnfhm33/7tPYJtFr/PfvVAbPQn8NsP1A7B", - "fdHbyJHWN1IYmsdBB+NqgVSYguanoQb8bX+gLoVg/mYEkiyKnvQnWk8SEQR7D4PZA1ih/z1lUiLcmRv/", - "K25kdJjb6btbkf1ibXoCEBmxn4PaDoOp4j4279NJxmNhQik6ds/4x6MQ3WPOibWj8+L7Z93OuU7z1By6", - "y6eIX+vsfeZMid87NXTZnQ/3j6X5vKx8s8pvUezcWB6iA5EnuVclWE61qXvYQEplIJvM2MzplYJT+FPB", - "RIwdEh9txiMIMZghT/JAtSVK7rN3kLuUzQse4xK9M8TcEalzzGQpuNZtwYE6Oj4PEbY0L071+TmEGIgE", - "dpfMWOZU7Ez44ySD6DuDkWBu94xz0H1QC6hN1CkMZ4Y6g0E2iChHNlUKVh2+EWBLyhmYBe8TQnRCMNpA", - "nZTIq/GqiO6HZS1jrEySGgBOf2K4Pvsjwh01PJZKGLORaYUrXfRppWadedruPSdePe+QKpTrqjtBIwV4", - "qw1+UIckECYSRVtUScDjL7ybl1jJK/upRtofvqN7XMU9rx8evru7NVu7NQV6d6CMsGHTFS3g9pNqi+tH", - "kP2B+hOvH4t75FDFF0EHf8O7pVtzHOIUh5mEty0ez7+GrbRm97Adbpz0mt3yKRmG2HpzUXDN3shfE+o3", - "0YnHE3XaPQanXUI4tr4KhtHQBhM7jFSTRLjLP7AY99kh/ZUOItcFZxEX70fJnI6vqU5iDxXyMUpyI28F", - "cxY0oLQqTeld4HhlQXYNi7jCoKVE8FsBZ49PfTRWp8ZHFY1lZiyD533mvfZ+aZgMuOEYT4iDYsi8Cc9r", - "yHWdG0hvcYdSNOUSNlUsEMnMHZRFYCCAVCEgvmvtRszB4eKna6D8iZ7yuauFQs1ZpnMV92wmU+ZuHypC", - "LBUBAMEqlrcyznlC1dRt5Fdwl6DVOfSB6NveJFaGwi63FGDwtrRnoUoPY/sVma1hIzDYMbUboCzTzRvR", - "5wxU9yEwJF+DuJR3Y3Vlgc4NggyfaEGLBh66jmco+LiLwr7/okt4KSH82q0hbkuYc9/HhtDDTRcRvap7", - "7jhpXscLweOjkge2bjofaz2xETLycTkXHAD+G0ZNwlm4tPMePP1u0BisGqIxapzRW843+LibJ7zqZH+i", - "zVPvyd92A4H33rMLWV1M0tejE3/DhwUfwPsYC4qMQo3rGOA5nmgJl+A/2q/eo7Rfok+p26mIHHIrjRzJ", - "RNp5cPp8NSLxi4wJEF3fVTmiNpKDOOOT5cNwMSUDEN1VjCA2XqmPcmu16obQemdeejcCd/3KLMayQ9qb", - "QoYgyAWH/k7krUDqHbKvE8GNwPgqoj0yjAcj+PePXTb/UMamSbnMau9Xxz5B4IlkN9T/UM3jKvpKjmzo", - "SkHPhcvEGWGnbCRSE2FRoq5TYlBrVjM/C1vhWnvKI7qe1K1+98NTO05FGMRjTPPPwlZe88k8QnXjW3oU", - "C8nttnVWbiCFe6KNskQ69zAbl6bJjezLbpYzz3VWWT5/Mgf4n0JXmUdZUiCwub4R8zWq2gNrhI5AfhOo", - "5VIiVAAnwienAiWrRJszUHVkOJgBDcQnaSamQqH/YJl1p8uMEAPlOlPPnMO4LV6kJtL2x5kQsTA3Vqd9", - "nU32Prr/QMTg3seDA/whTbhUe1hZLMb9KR4ZlK081UpnppwRRwFGfryG5YagdyKaCgBZMuSPxGXSce3j", - "IVE5PdF+WWSK2na7wIKCtHxNFguaEWWvG8jlY+yMUsp/o7K74jfisgwN8CRm7RKw4z0t4spDDZJu91IE", - "Ii1aCtmpI3jRqokgWjq7ig5gJu8XXfEAA8SKBfKRfw9db50kzWoQMS3ZLeE+Iq7wnnbawWNRut/ZkiFa", - "UtZVk7biMa0wmpGtWgGVRPerVCzRE4CctDK6MWxHaUuApwSBVYhYSKKA6OJbns1fMpuDv3MGSaplGGNI", - "RwWIo2Io+PbvMS4BEZO8wBR30q3AMFM2JTzBVJzDO6EOsNeLBij4GvxxmIfpcVO8Mh36tFv09PR6mUgF", - "t+wt6/Uwn3Wf4YMO3hrwSWdYp2MvPbTkE+3PEtjptvqVxOsrcbZhZwpzBJeHW2e+P6ZF6WEzGtQrJbs/", - "0cIt5tI/yNmDCdxfzcHoxobOnQctE8FwNGvFgpfRv/4x9x9E+piHyGLgsiO8rSpwglRwfPuURaDKgLno", - "D9R5pscyccpTaSVmqZ2XgrnCr+hhlqI8wgOiwbfs8HxorE73oFcIHjZQSBtQwLr1GVF0AnYb9hkoSPMs", - "wV9R1wD9bw75eQAXMFDD0PJ1onl8DRl7YjL/KYU0v+soToeA4mWt6zSm9h0dn/t3d8BHcQpyIa6EgDaK", - "5ybgTXGTUjsnOluYlj47CSEbPQrZKDehYteLgSp1Y4xp2pDknkAOveu4m0r/XOn6QsBPgKTgFwyQBv6Z", - "i2zuZIDPhAWe/oHyafSjOdOJs4cRNMSjf2QCIv+qKG3YlD/G+gMVJG0YFmXIYmlSOkE4Gwlje2I81lkl", - "sAWDXqpSh94hCkYpZpeShpH+gE8EQ3CmV24p3AYylIpXJpkc+svGkGjGuYLxWzbXOYv1QLlzWgkR99mh", - "ZYng7k6j/PMKRsK5zyEDexTWPEAaLsLAGOsuFlnu9o67CEmDr7svSoJQrHEX5gg8ZrRR4CAmKfJhFLh2", - "AZIM8VgGymZcGX+lecHkmHF41syK6EnXG5AZ1yrPEmfIFFqQAUSjGI9FZD2O4IzDnsc7HKJiRKIIgoIk", - "8WcfP9Jbb5rplE+cCQUKwe0mTKjTzvAwwkmaFWxYBGr8ZYjQaHs08CG8ZRPUQ0AHJQnr2UxOJsKZvgOF", - "M4uaK+ilqtKqjf/0OuYo6MtuJ+wAA+k7DYEO5f3vdQaJkQYMZDakjg493ayzuFiu3C2UgkSXdgvIb9BV", - "TqXTW3chFn02LKsm0ksGFVNZG+iMiUROpBPT2ug3FYOmMPWqAokMyNSExKvOiw7oiI7PC+s06E6AXfPn", - "eMHTV4QWBt6j8JtiSHXJ/h8eFAmzkOEFsa7X5ZjthQCmq9e9vxLIWzUgl814yv7///f/Q+VpxIwrKyOg", - "Nz0/vDr6hS2HhNezkdJX1w35AaUeYJgqG34eYOz+oPOinB7w4X7YskN4qNT1hjZbm27MnMYGC7/eY7HM", - "3D5kO8CAsIf8B3vCRn0PwopUwx7zZXlbI+oN6j3PTi4DEhnpnWAlFGCg1VjcAvGXaaeu6J5HpJA15ELl", - "ANamzf9JpgaeHHzvITU7yiFDtLRXAQcdh1GAF60MrNutsPxWRddnY5aRTg97/+C9T/u9v/Wvex8+H3Sf", - "/fBDPUTMJ5leu4OjnSuhmjYSytLOr0uoWuQnoHPqms6p5dm0POt/MtYfZGVthwaFgekdVkICIYOCoDPp", - "ICRVYPqMDpkQPowI8kBlLJTNZDkangqDlbrnGYD9TdiIxJXfAR2K8eNsSLHre+cB/8EMdxEUdejmLb0u", - "tsQQyYlAieJyU4SVHywE5xI9uHGmBXxwl/E0FRkr9aeCS9a0XMROU59o+f7iTXgsJuNKLJhWYo2l5A2l", - "LksAF8RtqojjXrPs2f7zvyIDWrfYem4BI8hgQVMRdAQtAPZilIgGxtrqXK64uhQYcH4G4amwKItwxJlM", - "MfhhQSaDVOw4yyUQfVC6JLBWi4+4I9cCCH9VD9ZVixn15cvC2R+kIEDxVK6//Yfcf5/v/219OdfBREZL", - "t+bHCb5ZtOn8LbtxnoS/OqEuD4lKMUunHKa4fEE/xPsxXF6KuzG4xOjOXLX60yQ3S3OPr5utYkZL53NI", - "LqvJYqJz96keI5aP9j9b5ql1j/u5vJzvKSqDJqy6DF9Mph+cslM/nJbCMzZ7USa4FdeB5R4EKa8Lc4QP", - "Ay/HU8U6VlvZSJgOVtGI4Di/Ik8ejpRxSJeOS9PaduWQJaPFyh3Dh0+9ctjKObfTB4e6hEXDIcYP253P", - "15d7q+1rnav4EWNkoOeMP2RlvT2+YlFfo9n9da8nEE39Cywl3XFaryLx2bgdev1JAoHHRNg6ih+bZ4Cj", - "+I/TcxZuLaXbjr/EBMqFgjbKi1d/ObSN2j+W2T9kus51Fdi1Qo1oLVsdbiXOiPGDavL5UBBYVUrKDp+1", - "hFwfNjISaF4f9A7uZt2PMTCXgOiVJ/hblFxarLIacvcWFDR/9d5Woo2NW4i0v8fvWJ6VLvMzH3ICNrWr", - "a3el5A/UCtFn/zA2Zno8FplhRk4U4IQB0i8BpPsGyRYfqFiUf+V+5hneZj/JlJxHPJpKcet6MhJ2sRbY", - "aPUhpaV95+boW9l43SUgktJwIS6qz36Rk6nI8F/Gw9AzMwOw2cK1Msots/xGsESricj6A9XDlTD2BftP", - "t9pYBTvoMoIldQsrYrbzn9/v7/d+2N9nZ6/2zK4rSC7iasHvu2zEE64iZ9K5knuwAmznPw9+KJXFhasW", - "/V9dv56+yA/7vb9WCi1186ALvw0lnu33nocSDStSkpZrqKbB8e1/KhzfNFWAbub/hl2GH4xt9IK315u0", - "ex+kOK8WfHT/RZTngmtyAwUK7iUP0ESKs6o8nK0EhNhttQboCpp4UKA6qxoFX8MpvZnlGeagRuTAlpQK", - "hfXBF/cvIlg/C1seAeMjDABYWr0NBCuRxsJ9wTRK1htpgI7WbHkgfZuyVIy6RpiKi2aCcBrfoDS5AeI7", - "BaZ6byM9M33bfNE807dwC3zCuP/HuGRCnH3h3PkGVxJGAA/88C74MIWQCR4HB0KtPrgQPCb3QTt1AN3x", - "pqmr/2vRCDqywvaQI+nBNg0cMLW5tt+YOEFmb+UJdAPxMQKPk+sSwXajhljmOX+6RNAGQvWtgbpK/OGU", - "tvkNLvWlsMvKosyNvgfc62YKbqC2MoAv080hooeI6F08YBPKiM6KaCw8mCjbKRMzTXoEU5L7DXA33kx5", - "tKieYBk1hE7EwtjrNazz7hup6NGOtCCBuJLp3YZvvtvZNsqCvI9FV9eHWdTUsC2850G9W7cEf/WNq8sa", - "oJ8xieFmG8a7elfiX3FwM2GkZgnFTlJYnw9OWHB4LUhg0/ZBb++jbZ5NN0dcpu4vgXgV0S263U55pJik", - "VTtmS9H/h0yruG80zH+ZbcDLWGwLIrrFjiBn05otsamruGnnDNT6rbPeZVzxEA/Ugou4GauNfL6Ptv0a", - "I+SuAJK+6oHzx1CLmLAvtq3rI7iauALftg/iIgR86pvr8w6wSzpx6vXgm15Rbre/GYVn4e17AoVySHP4", - "L65UFsV1a8VytwiQt3AjsTyzr81v8NUT3UVKTWwepbIlYjwMu5Y44r2S/8xFHU1EsW/vaDpaRSsu0kLb", - "aMoeG7b4C4kjDqbs1ifgQDXZyN6D+dz77Bflnph4BWJeLUqkTguBXHC4gBOFvCbkQwkrvcqPst5t8ryO", - "4RuXEoPhv/GldNOKcg0R1Fu5yhaXca9gCq91nF2Co+m1Obklp8qftpqLTjArPlrsba33a90byyVcwmEY", - "tfAAlyeeeFyPS7d2yubudDtTwWMY9efO/+ldXp70CNCud1XLmn8mYsmJUnjMXPVADUbJ4TuLinC38l7q", - "30aX1GXNU+j9tyjIMNFLs0wIWV51t5bpTK4LIAOcuDYO4OOSEciXnMF/YjzCu4KXOxFspmPBdnRkecKw", - "TJcB8cGPz5/v9lmZHfzH58+bujlDHuDabv2+3/tfHz5/331elzKzOuvsEd3TW3pmAkrht35Yg4vNnc8+", - "XnaTMLxET8xeMfX1D6N6YnD7NejyBZEh0tRVsu2VFW2CAka8lqu4vhlkTKyPGakw55U4MhcFISS3Q66U", - "HDPsO5PGo7Wt2LrNJ9Mm7ZTGXt9a8cE18ZR1vtip+EZPWh6HTrC+6hOw7nRxncb86svLk7ZbKE34/C7D", - "9EyEW24BTJ6NpM14NmfnoTSLnMKGN+pxJoyHk6YkbEhB4xMulbEVdrssV4Clr7RiiY54MtXGvvjbs2fP", - "MLcdap1ywzioOafuv0v5RHzXZd9Rve5Hqu47zEH7Dvk8vxuoW55JPkqE6ftf9hNpLCDQ7+x+RyD9ptyl", - "3p2MBSNiUWQtVjw1U227GFvoKwLuZ+Gq2nGfXYhxF3HS/73LPjOi6r4U0b+z+93vkCkaWE2IJrrMEM3s", - "NNP5ZErJ9TM3QMPMXEXTTCudG9+fw/PTvv+ZjqcC+cFVcsc4/b2s4MG3NlBIjH6kY9GFie2ygjr9lPD5", - "RzqeU9IqrkAGvPg+BgnWo1haAPF3UyjigSIwCL9/+wM1ULhY7ruROzgg7JHS55FxMRMFqBzmDvbZqaLH", - "ph6RH1KeFWQDAyQ8TtRYZ2KSYb18RInF4XGqSqCIyYc6FarrcxSxaRNQHbBHA5UWrIsest6IRICrhn4v", - "DctV8D/2C1l0f3EFXmHjRyT6kPGp7xR1PFQH1JnsdEzt93BGwAjDKNRIEM0eeFcmwtJFtltahCKPH1yj", - "IGEl/hjMUvVbpE89BfG3mroCEH7YVY+hIT46PSRtMq9zhpIqKPb/EVpaT+GLWWrrC+Wg1fTDbY365MKw", - "Tb5GQP1iCICmdAk9x71do6Rbwg7R8Q7HSLPv7hy/cj15MujE0MIXEpRKD5pEpCDMyOibr4JpIdKzGUAo", - "FYdP0trd4kXApPxOrZWBS/jqSYUAmviyUkBdaBID+PMXxo1bXn3+oOX/TD+Au+1GVuEZa0XhVwk4f+td", - "bUXNK29o4eqd5zJ+yO1+qyV3o/kqwezf/fpNhj85dSQniicIpuRvkdvLJOLJrJXKC/zsX0YucTz/LZmP", - "F4MJsEScnV/9R2+EGFuPIZ54T2v0aPmDBb/6s6XziU9LHFTdQUl/+SYTQmgBmPFr9hDhiGUL2wq++pfR", - "XDCcL2zHYRea7LhXc+CxQ6/8N+uIL85XZkiCHiSpOrfr/PPF9OrcrnTUfyGd9gCHcxibK9bS9eznX+c2", - "zS14oBI5Foh6+99vs0/2NluSe53bjf3omYgANX6yV8SI1GtoBJy48N8/Kb5HaGU9B8Fihj8V/HLIHl8I", - "eCnggaSZuJVw/2W4uCJmtzIWeqMnypJcUMZxoyb0Kcll0Vj5dH9ahIOF3Gy/bB6azOqALdBl3LCUQ7Ct", - "1azUNYj8Iv+5nrkjjIgCCmDnxXqlCfU2Q8SCxq1/fOe9T4e9f+z3/tb78G//z1Z6GdZib5Y+f3BSWCHs", - "tLIV7Rr+2nstlTRTEfcOax7HruRMGMtnqVsLwH6sLsiYCvfZzznPuLICl2Ek2MXro++///5v/dWvspWu", - "XGKs3lY9oTi/bTviuvJs/9kqnQGwqzJJmATI6kkmjOmyFGjdmM3m6GVGTOrqdF/Abjocuz8sky3kkwki", - "DwC7HJB/S8WQ26dMTp/NcfcUgwiRwAc1kcD33zB8AZI9GNiiAgLcH0VZJRKPrsZcc1xst2oPNL1Dztaq", - "08y3hrgBS4lQSzv6DTFQZaGXj5aMzZOkVO3GEzvj2U3zCzuO0zDOnAaNGeG6K5R1ioAvHlBDtYBRP5YK", - "UFtRJnh2IzLPQfMHPjdKnzpBxuXZ+XN3JkRTnlqR+TLLiUdnPLt5aoOl0sYThlxv0Iemu94ZzFPYaP9l", - "TKPDOA6SibJC7/lS9byaL2Ry872BePCrw/6fWgyrjaw0mw9WHYF0yH6DyKMwA4Goq6xj3qlkTjwBfpip", - "yNjpMYu4QnaqiTRWZAgNzUFr9beRA52uEgOdPr0UlNrY/u5EYfhflhTK6rRqALZfkDRZHwz2v/ktpwAC", - "iP5KRWacECjL3upY9P8wPhiGXZycv+mzy8BGg7QVU5GkLwZqoIbD4R9moFyzfffLnd2XrOl/e3sMTuOZ", - "sFMdV0oNOsA8NujUFd/bY5dyorjNM9ENzBtdf9EJhEQA+16tdSnsy7UAvXadP7zlMgFWGOqSGxLQnP2P", - "Xo+9Ovn59C37+eTtycXh1ckxzAQ7O7n65d0xe3N6ecV6PeRBG6ge/PEFG4a2h136B4R9h3+JmbSnMz4R", - "Q1foVQE/bTOdvGDDKIaSccalwmQG909nOb/WGfzC/XuirX6fJe7HlE8AmMn9jOF2RHl9SQFw7g8wt+4H", - "SzRc7uexTJJTlebwj5SoK93PSIgH5ZAAsCAldL+EWDw+go4RD+4VH0HBO2mjKf1DiTv6KUq0EfSzUCbP", - "xIXgCf1Cjt1d+QrCpPxQz8qDfqN5XJ4DtwOULf3mrbB3Ors5jROY5j+gcB5AC9y/ptamPwsLc46Bdi/Y", - "cDG0EKrE3/E4PsqN1bPFP7iP6/+SiZm+FcXfSn8qhG9YFq+Tt8fthGugSE56JCdeWsFO5EGGgVYQs22I", - "UksrNqR4oCEGBgKjxTjTn4QaqLrgySiRAlmJioq58WMhuh8flka/7BIvgtOeEczAQEEQJo/jLuA4dYkF", - "CjjEqff95SXY2QWaqUjfOivYVWGKKMvhzydXbA+L7FmafujOcmznriciGp6/uyxK4RfD/kAd6dlMK+YE", - "Z5zoO1PWZRwuqrTHdgYdz6VACqYf6RlqEfywJKc77reRhlCsTAj2E8NPajdm6eNEqhv2E5TpKx0L0x9L", - "Fe+4H9lPKAbM/aOf6USwn376iQ06rsygA6yMz37E/+I3gFSE35zprILM1u/3sedyzHb+h6thlwJQlbhj", - "cBDtYM1MacvGzn4tDxX0yA6UezlQvveQ/OuHuqQydgadPTtL9yCqMVWTQafLwN/PDn7c3y/qrqrHnc+u", - "1vuSrr7SKTEBj6RyooZ8SPFc8RmRhMoZhaXOdJwnwvhjjeXKSqBuEhgqiV5jPCl4BAqlz04+BmTNW57k", - "xHxBfGovmQCiKLyCwTlYUu87/X5/d9gFThntjiAScWCJW1D8+G2fnUulROwUuI2mENZHm6sI7u5FOhND", - "lvLohk+wPwNVbEvaGX4K2BDHv7Pr90UUp24nEI2oYbnKhLGZhGvlsbiFPUfCCYG/EDrsJmsm0BY2LOGf", - "JGkTaZi+Uxj162yTw/NTP5V9duEmtwu/I16crg+n7g5UlHEz7bJ37866tABWRzoJLA6x65eeG8AMMBaS", - "SlTs8z/ht0fvT4+f4WRey9jt4RCMhytV0Bo6VSdNhWKtMvTFeGgFzGml4MmB+vtZl7kb7C1PBF3ms9zt", - "VJBAupD71wfX1VJQfVHz4fmpecmkJ3ljhqt4pD+KeEV0Lun6C2fHPY2dXGqhcmWvmorAIuXD1Uc69lw8", - "iZxJi87Hv7Iz+cp78HB2AaIBLx5XJCKZ+AMY6bi7fpopT0TMYi5mWoXq4d0GXNfw++8MVa3EXSKV6MXC", - "t0oleu7X2BXE/1Ra9UCIcnSyPN/f73f+zAfnyqS2izDeoRBjtyNKDCa0J3YfdAs9+P7LRiVWLxil4How", - "dFoGJ5uIJ8LqTyLTe7E0TumtpM7Hh1HX2N/PkD7I1QCw1Zq5WrrussuzOIFZH7Nfrq7Omc34eCwjZyxJ", - "22dHPEk80rUTX0j0kMZVeSeThN3xG+G29EhEPDeCvVfyJuNji3/ludXurMUDCb5F4v5AaOqVx9/PaoGq", - "cZiXbuRX+h8i0502CeTwfc/qnhslo7mKH2VFT2MxS7VFNzXVDPMq/KyWpqi136C8tEKtXtkLYazOhCGK", - "K2w8DDYQMRa96DKeJPoOHjVgvqvdxXcMeGORcSJwybFseHj5+xlTmqCygVDU0GvLVCSxM3VuatkpT9TD", - "Vw+n4wkWDyt++NqFT9ZCzUPyEV4RWShVpcVxlgN+/Hz/OZPj0nd0aAbKs9pT82dhr0J/nlC/h0YunXVS", - "i7xdP8BtHcZL4WgNE9hm1boFD9WC0uQZkccj0iIuWeNSgS+RWnD2QJFuxIwImr5kNMzBZEAzLn7pn6nL", - "VRSExDILsmKEtc7C30g42CWWYuJWlLvuZN7PCuAk4f56wcY8MYJFieCZ8cQGpdHWCBtwalXF7cnMs9BM", - "mT7rz7Nntpb3bxizk+i7HrbR8hp9eCnsmp3l5fzZ/kFVzu84CnopsKWQ+ZchF/bZ/r4rB+4I47ZCIiKf", - "7KlT25PqBeOFCTLllvaBq728H3f4AikeXnaVtlOMJEEDBi7wOvN7zW8vb3nsNm6rlz4blMlwNtGxu5ni", - "P8/tl9uJX/3Oe8wH1u07ZMSXzZC7fNixWTF2ShBE9WbqqaJ7rsIQzeLhvugCRox22YTbwo/i05gXOlpW", - "Cvu4C+FrY+REiZgJdSsSnYrCaKVmDeOxjwd7tv+85u9jmeCD347SvnkfI0YQZfDtd6bY2tIUuxu2/vP9", - "fYa+MhkTgTneXOt36yiRpjg7Ma72icLPsS1o4guFnxfjpEWqTSaF5Uixt+Bi9Csa8cwzGxfrbdxQVCT6", - "uL9r7hFYIY8ikYJ45bZY6dWy9hLPGN+VB/DJVvYfrUSLLbH5dlyKUF8EJhLAdQWvldVg7aJt3NJ9dsKj", - "KYOHKICtIJiCGRvK+AX7bMQ/7wcDFXPLX7DPfpF6TiLc7wcDNXQnLq4OMRyjr4wchL2ZVtpqJSNwFXqv", - "NI8ybcyCyiSX50vG2RtubA/WtHd6jP4MrubBEnAFVXHKwz4EZ0MmTD7zLgwcdp8dZzolPylk5aFITHhq", - "vNk+lPEQQSRegAmCr89C3ooY/yYNYjDbKVfsgPGp4LGPYU1cX40QCj7t+iD1O5E5VYJuVxgBeFnz8Vhk", - "fXYEb02GmanOk5jZjEc3NbVBOKywIrLQ3z57DVglxfCNt1EWpgzc0EWzxe2ClgqfyODyIuAFBHv9EuJt", - "2fDf4Xlg/hNPkiEimlaq00kM9FNwgXH6mCTcWMFjdH/eSTffU54KDzExEUpkMmLDqiYc9tlrnQXLi2ZP", - "0HWJ9u6vQKiOSCNsx30+h1cuJ225IbA9HeUzoVwpeOwd7nYr6nyITOxO5nQ2C4DWMw/XNySb5y/QrWP4", - "GJVatwDAGM2p8jpNj5H51eGt5be5cCLrOc7BQDTV/dRn7+D9JWNGqJjt16yHX95UG4lGdbs92WVGVzcW", - "PP/gNhJum2X0pEpNcYshfn12xW+EceUiEUND4MgeotwM8eCFV1lsWBihLDTnFBLPre5lgsS4aC4R3Mkk", - "CRIGRPawSrdCU2mARqrgOMNI3CKAu7IJNgONOgfB30Tg++wC2PhgS7PI6RNu2cH+s+cvoUAQZl7SBIBV", - "kGdjHgl0349lZixu9glgimWkZfqNVG44I/U5L0myHRvbA7KGWp34b1ocRt8cgtXiCNyKXorsVmS9S7cf", - "gwZodcDTSz2GEfTgmX+tmxEvoaU4NiyNQFBwNEhrUPWC3X163IWASJPySHRR+YkMH00DgNRMWO7mcTmw", - "9k0IAsGYCXhGfUrf41Jjq5Kyj0gN0hyUMLFM//ECqmtqb/Go022iLQkxqrAkMXL1O2kuraTps8PKylL8", - "Ysonwidy64wdHZ/Tv/oDdV76G4aT0OubN3oANelW8tKDVb9UxUIh9+HR8TlCirnmcyMgtnxUetyiMjvO", - "qBhCPN5wl4JvEIFvlhvLhDtkOGZv4vOlmKV2ziA6HphVYzGWSpbsm/cXb7ysmm5VSNlOEYHDlQ9TQPnZ", - "9eFxMIaBGucKrSbsFE41XBoB5NxqCqfoszNqjDnRBd+pDUEY3tDA128FGR4DBTa0KccD6dz29Ljnn8ox", - "3Mu8ZMKZ3MHsgMFIcChr49YRDlNcIstH3wF/wGLT0DJPU3A0ScVqAoTqDJRDH9y1uHs3uYlSLBBs9ShO", - "r1FcYEtX2Z44s9ImQXQ63Q5YF+7LIO+dF51SbJFTzwhn+aLz++eBYuxGqvgFG7iGBp2u+w1M3Av2Oc+S", - "a0rQMi/Y77WxSnt/GXQ+3EMxNykvGNTJYLu5WoGWzI0BOkoNMFbaoe4rGAwtR3VI/VAEgPQuQehesM/u", - "9HAlEcF80Ok6CZMIF3seINTJDX9PVaDkrqgjLZX8DP0ofYYn1cAprAB9CdNCQ6NpwP/ShniBuDls5xq6", - "32WfcY+Z+90Qe7VDU0btYcDSH2Znp/jEC2Ufvumyz4j8dhq/oD1rft//0Lc6vcY/XMv4fhd74/7v/sMA", - "0EI7TiGtl6aS9nqQRLkKnkqkYCTtZAq0MHz6ryJMZdmhiqsSUl71+25LQ6BOdX2hrJsNbZLiFMdzJsBU", - "QhnPMG7wOu5EQom7ZE5nUmG8fGNZGc6u2c5SajCF9z4vcRosmsQzfSsQHrNsK43mFfu3zw5VCUeU8cRt", - "2Xk59TO8wnAViQTThRfZKouQ50IQWuFArMF/KOUcR/b6d94b7/f+9uHzwY/37bKOa3yjJRGkOGTv5fxC", - "KEQQCs1rxGMz6cCQ5lWvEghhC04gwLBNMAmxBGQbGErQ1aR1cp2JcZ+91arHc6tNPppJCO1LuCtyi+5R", - "DPL2YLLBeSNkRli06PbQaZ5w67miuMLjU6rJNdSLrw6IsdNn7+wUEtR0YgYKjlkE27cim0nFE8JdLFuY", - "5EZV/FZO6DX1tEJ6DnGogM1BfSwEf6BGYiIRoxYiSG8hRZAgZnMb6Zm4ztWN0neKDF38wgb/PAa6l8O+", - "6oxOXIbKLnmKpw9sABtbEdsJupffscUYT3jlKQV4HkAUZprkhj1nv8pX7kYWHin0rcjABUwBoCHy80+N", - "uyxGjMVXBF9OMz2T+ax4IAHc5pISdLckCQJYCC3ezUq7AB4GdFZgIC8JZ//bCH1wpZ4/2Tq8xrfAuoUo", - "PmIjMeGqCy9V0hpMhvb7jkXwKuDOoJFgegS70yntaoQbyDrQzGDeiIi3V6bt3E08gIu7/VA+2Ms5M6iV", - "SkHly1kyVChXCWQjiI/uz+Ka/igNhFmQ68C3iHUInx1TcnqN5uQI06lQcE0DaPA5E7ORcHYU5GtgvIW0", - "U7hCaAMXOoz+f6eINjUDLy/IZde/OlA6wEwraXXmX5d8IhC74iP0BHjgbnCTQ34SmhYDBd3CzHYYAmGX", - "u+a7vhi5KqjDnj0QRiRihJ6AKuCpyO29Bb1LGQ2Z8Gd88fjtJkRnRd343AfdLE2inYoZg4Q1CMumQ0UY", - "d2jNYZbgrRD9FX4puGI65f/MRTg7sd5wrmIGQ8Pxu+DK6Q4UJFsUWTuMj9zZZeFByU1T1w0Gp0p7/pW6", - "cVHiR8yk7Q9U1QhPjPb9m9OhXTYQ8bDz10mWq1hk5L/qo3wiQnkxYD1QaItQdNESXn5D9GbVC7PylegE", - "N0h19+ACu43S8FBQ3Vb15B1wbaxh7/jw5CdXa39uHbnBl7Rgj0nfLijAJbXXRgPf3//fAAAA//+MD1Qq", - "46wDAA==", + "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", + "3awDAA==", } // 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 8fc2031d..44f6a81c 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -18,7 +18,7 @@ import ( const ( customToolNamePrefix = "custom." - customToolIDLength = len("ct_") + 16 + customToolIDLength = len("ct_") + 24 settleDelay = 200 * time.Millisecond settleLimit = 2 * time.Second maxToolsPerSession = 256 @@ -204,6 +204,10 @@ func (c *connection) handleProtocolEvent(message cdpclient.Message) { } } +// customToolIdentity decodes the hidden name used for a custom registration. +// The browser registers custom.. to avoid collisions with page +// tools, while discovery exposes the original name and the generated ID separately. +// Names that do not match this reserved format are left intact. func customToolIdentity(name string) (string, string) { if !strings.HasPrefix(name, customToolNamePrefix) { return "", name @@ -216,8 +220,11 @@ func customToolIdentity(name string) (string, string) { if !strings.HasPrefix(id, "ct_") { return "", name } - for _, char := range id[len("ct_"):] { - if !strings.ContainsRune("0123456789abcdef", char) { + if id[len("ct_")] < 'a' || id[len("ct_")] > 'z' { + return "", name + } + for _, char := range id[len("ct_")+1:] { + if !strings.ContainsRune("abcdefghijklmnopqrstuvwxyz0123456789", char) { return "", name } } diff --git a/server/lib/webmcpclient/custom_tool_name_test.go b/server/lib/webmcpclient/custom_tool_name_test.go index 2cee94f1..2f970cf9 100644 --- a/server/lib/webmcpclient/custom_tool_name_test.go +++ b/server/lib/webmcpclient/custom_tool_name_test.go @@ -7,11 +7,17 @@ import ( ) func TestCustomToolIdentity(t *testing.T) { - id, name := customToolIdentity("custom.ct_0123456789abcdef.fill_payment_form") - require.Equal(t, "ct_0123456789abcdef", id) + id, name := customToolIdentity("custom.ct_abcdefghijklmnopqrstuvwx.fill_payment_form") + require.Equal(t, "ct_abcdefghijklmnopqrstuvwx", id) require.Equal(t, "fill_payment_form", name) - id, name = customToolIdentity("custom.ct_not-a-custom-id.search") - require.Empty(t, id) - require.Equal(t, "custom.ct_not-a-custom-id.search", name) + for _, invalid := range []string{ + "custom.ct_not-a-custom-id.search", + "custom.ct_0123456789abcdefghijklmn.search", + "custom.ct_abcdefghijklmnopqrstuvwx_.search", + } { + id, name = customToolIdentity(invalid) + require.Empty(t, id) + require.Equal(t, invalid, name) + } } diff --git a/server/openapi.yaml b/server/openapi.yaml index a6242b03..313de524 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1360,9 +1360,7 @@ paths: get: summary: Discover WebMCP tools across the browser description: | - Returns a snapshot of WebMCP tools available across the browser, including custom tools unless - exclude_custom is true. The snapshot includes tools registered by every open tab and by embedded - content within those tabs. + Returns a snapshot of WebMCP tools registered by every open tab and by embedded content within those tabs. On the first request, Kernel starts monitoring the browser. Tabs and windows that are already open are included, and tabs, windows, and embedded content opened later are tracked automatically. @@ -1370,8 +1368,7 @@ paths: Each tool includes an opaque tool_ref that identifies its exact live registration, tool metadata, and information about the window, tab, and optional embedded frame that provided it. - Custom tools also identify their generated ID and namespace under source.custom. Use tool_ref to - invoke that exact registration. + Use tool_ref to invoke that exact registration. operationId: getWebMCPTools x-telemetry-category: control parameters: @@ -1452,10 +1449,14 @@ paths: Add a namespaced batch of custom tools. A custom tool can be page-backed or CDP-backed. Page-backed tools execute in the page via JavaScript. CDP-backed tools execute via CDP and can use all browser REPL tools (see `/repl`). The source must evaluate to a non-empty array - of definitions with URL matchers, tool metadata (including an output schema), and execute - functions. The batch is added atomically. Matchers apply to top-level documents and nested - frames, including out-of-process iframes; each matching tool is exposed once on the tab's - top-level document and appears in `GET /webmcp/tools`. + of definitions with URL matchers, tool metadata (including an optional output schema), and + execute functions. The batch is added atomically. Matchers apply to top-level documents and + nested frames, including out-of-process iframes; each matching tool is exposed once on the + tab's top-level document and appears in `GET /webmcp/tools`. + + To update one tool, list the tools, delete its ID, and add its replacement. Set + force_overwrite_namespace to replace every existing tool in the namespace atomically; + omitted or false adds tools without replacing existing ones. Existing invocations continue. operationId: addCustomWebMCPTools x-telemetry-category: control requestBody: @@ -1493,7 +1494,6 @@ paths: name: "read_cdp_title", description: "Read the tab title via CDP.", inputSchema: {type: "object", additionalProperties: false}, - outputSchema: {type: "object", properties: {title: {type: "string"}}, required: ["title"]}, }, execute: async (_input, {matches}) => ({ title: await js(() => document.title, {targetId: matches[0].top_target_id}), @@ -1525,7 +1525,7 @@ paths: required: true schema: type: string - pattern: "^ct_[a-f0-9]{16}$" + pattern: "^ct_[a-z][a-z0-9]{23}$" responses: "204": description: Custom tool removed. @@ -1586,7 +1586,7 @@ paths: - REPL: `repl.help`, `repl.write`, `repl.emitImage` - Browser control: `cdp`, `drainEvents`, `waitForEvent`, `gotoUrl`, `pageInfo`, `accessibilitySnapshot`, `click`, `typeText`, `fillInput`, `pressKey`, `scroll`, `captureScreenshot`, `listTabs`, `currentTab`, `switchTab`, `newTab`, `closeTab`, `ensureRealTab`, `iframeTarget`, `waitMs`, `waitForLoad`, `waitForElement`, `waitForNetworkIdle`, `js`, `uploadFile`, `httpGet` - - WebMCP: `webmcp.listTools`, `webmcp.addCustomTools`, `webmcp.listCustomTools`, `webmcp.removeCustomTool`, `webmcp.invokeTool` + - WebMCP: `webmcp.listTools`, `webmcp.invokeTool`, `webmcp.addCustomTools`, `webmcp.listCustomTools`, `webmcp.removeCustomTool` Browser-control methods are available both directly and on `browser`. The same frozen @@ -6484,7 +6484,7 @@ components: properties: id: type: string - pattern: "^ct_[a-f0-9]{16}$" + pattern: "^ct_[a-z][a-z0-9]{23}$" namespace: type: string WebMCPToolSource: @@ -6518,7 +6518,7 @@ components: description: | Tool metadata follows the [MCP Tool definition](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) and the [WebMCP RegisteredTool definition](https://webmachinelearning.github.io/webmcp/#dictdef-registeredtool). - Native page tools may omit outputSchema; custom tools require it. + outputSchema is optional for page and custom tools. additionalProperties: false required: [name, description, inputSchema] properties: @@ -6568,6 +6568,10 @@ components: type: string maxLength: 8000000 description: JavaScript expression that evaluates to a non-empty array of custom tool definitions. + force_overwrite_namespace: + type: boolean + default: false + description: Atomically replace all existing tools in this namespace with this batch when true. additionalProperties: false CustomWebMCPMatch: type: object @@ -6579,18 +6583,13 @@ components: items: type: string additionalProperties: false - CustomWebMCPToolMetadata: - allOf: - - $ref: "#/components/schemas/WebMCPToolMetadata" - - type: object - required: [outputSchema] CustomWebMCPDefinition: type: object required: [id, namespace, kind, match, tool] properties: id: type: string - pattern: "^ct_[a-f0-9]{16}$" + pattern: "^ct_[a-z][a-z0-9]{23}$" namespace: type: string kind: @@ -6599,7 +6598,7 @@ components: match: $ref: "#/components/schemas/CustomWebMCPMatch" tool: - $ref: "#/components/schemas/CustomWebMCPToolMetadata" + $ref: "#/components/schemas/WebMCPToolMetadata" additionalProperties: false CustomWebMCPToolsResponse: type: object diff --git a/server/runtime/browser-repl-help.ts b/server/runtime/browser-repl-help.ts index 69af4757..c15b3d26 100644 --- a/server/runtime/browser-repl-help.ts +++ b/server/runtime/browser-repl-help.ts @@ -179,10 +179,16 @@ export const browserReplHelpRegistry = { description: '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.', }, + 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.', + example: 'const result = await webmcp.invokeTool(tool.tool_ref, { query: "example" }, { timeoutSec: 30 });', + }, addCustomTools: { - signature: 'webmcp.addCustomTools({ namespace, tools })', + signature: 'webmcp.addCustomTools({ namespace, tools, forceOverwriteNamespace? })', description: - 'Atomically add a non-empty batch of custom tools. Every definition requires `kind`, `match.url_patterns`, MCP-compatible `tool` metadata including `outputSchema`, and an `execute` function. Returns the added tools with generated IDs.', + '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.', }, listCustomTools: { signature: 'webmcp.listCustomTools()', @@ -192,12 +198,6 @@ export const browserReplHelpRegistry = { signature: 'webmcp.removeCustomTool(id)', description: 'Remove one custom tool by generated ID and return whether it existed. Active invocations continue.', }, - 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.', - example: 'const result = await webmcp.invokeTool(tool.tool_ref, { query: "example" }, { timeoutSec: 30 });', - }, }, } as const satisfies Record>; diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index cdba4fcf..41ccaef5 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -10,7 +10,7 @@ import { BrowserReplCdpClient } from './browser-cdp-client'; import { BrowserHelpers, buildBrowserGlobals } from './browser-helpers'; import { formatBrowserReplHelp } from './browser-repl-help'; import { CellRuntime } from './cell-runtime'; -import { CustomWebMCPRegistry, type AddCustomToolsInput } from './custom-webmcp'; +import { CustomWebMCPRegistry } from './custom-webmcp'; import { createWebMCPClient } from './webmcp'; const SOCKET_PATH = process.env.BROWSER_REPL_SOCKET || '/tmp/browser-repl.sock'; @@ -396,26 +396,11 @@ async function evaluate(code: string): Promise { await cellRuntime.evaluate(code); } -async function evaluateValue(source: string): Promise { - const key = `__kernelCustomTools_${crypto.randomUUID().replaceAll('-', '')}`; - try { - await evaluate(`globalThis[${JSON.stringify(key)}] = await (${source})`); - return contextGlobal[key]; - } finally { - delete contextGlobal[key]; - } -} - // Request handling -type ExecuteOperation = 'execute' | 'custom_tools_list' | 'custom_tools_add' | 'custom_tool_remove'; - interface ExecuteRequest { id: string; code: string; - operation?: ExecuteOperation; - namespace?: string; - custom_tool_id?: string; timeout_ms?: number; } @@ -430,7 +415,6 @@ interface ExecuteResponse { content_truncated: boolean; timed_out?: boolean; exiting?: boolean; - result?: unknown; duration_ms: number; } @@ -475,34 +459,14 @@ async function executeRequest( }, timeoutMs); if (typeof timer.unref === 'function') timer.unref(); }); - const evaluation = webmcpExecution.run(executionAbortController.signal, async () => { - switch (request.operation ?? 'execute') { - case 'execute': - await evaluate(request.code); - return undefined; - case 'custom_tools_list': - return customToolRegistry.current(); - case 'custom_tools_add': { - const tools = await evaluateValue(request.code); - return customToolRegistry.add({ - namespace: request.namespace ?? '', - tools: tools as AddCustomToolsInput['tools'], - }); - } - case 'custom_tool_remove': - return customToolRegistry.remove(request.custom_tool_id ?? ''); - default: - throw new Error(`unknown Browser REPL operation: ${request.operation}`); - } - }); - const result = await Promise.race([evaluation, timeoutPromise]); + const evaluation = webmcpExecution.run(executionAbortController.signal, () => evaluate(request.code)); + await Promise.race([evaluation, timeoutPromise]); return { id: request.id, repl_id: REPL_ID, success: true, content: collector.items, content_truncated: collector.truncated, - result, duration_ms: Date.now() - start, }; } catch (err: any) { diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index 05cf94b0..a914d655 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -28,7 +28,7 @@ interface CustomToolMetadata { title?: string; description: string; inputSchema: JsonSchema; - outputSchema: JsonSchema; + outputSchema?: JsonSchema; annotations?: Record; } @@ -48,7 +48,7 @@ interface CustomToolDefinition extends CustomToolDefinitionInput { registeredName: string; revision: number; inputValidator: Validator; - outputValidator: Validator; + outputValidator?: Validator; pageExecuteSource?: string; } @@ -121,6 +121,7 @@ export interface CustomToolSummary { export interface AddCustomToolsInput { namespace: string; tools: CustomToolDefinitionInput[]; + forceOverwriteNamespace?: boolean; } function clone(value: T): T { @@ -131,8 +132,8 @@ function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } -function newCustomToolID(): string { - return `ct_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`; +function newCustomToolID(createId: () => string): string { + return `ct_${createId()}`; } function registeredToolName(id: string, name: string): string { @@ -300,9 +301,19 @@ export class CustomWebMCPRegistry { if (!Array.isArray(input.tools) || input.tools.length === 0) { throw new Error('tools must be a non-empty array'); } + if (input.forceOverwriteNamespace !== undefined && typeof input.forceOverwriteNamespace !== 'boolean') { + throw new Error('forceOverwriteNamespace must be a boolean'); + } + const {createId} = await import('@paralleldrive/cuid2'); + const nextDefinitions = new Map(this.definitions); + if (input.forceOverwriteNamespace) { + for (const definition of nextDefinitions.values()) { + if (definition.namespace === input.namespace) nextDefinitions.delete(definition.id); + } + } const occupied = new Set( - [...this.definitions.values()].map((definition) => `${definition.namespace}\u0000${definition.tool.name}`), + [...nextDefinitions.values()].map((definition) => `${definition.namespace}\u0000${definition.tool.name}`), ); const additions: CustomToolDefinition[] = []; for (const tool of input.tools) { @@ -315,15 +326,17 @@ export class CustomWebMCPRegistry { } occupied.add(key); let id: string; - do id = newCustomToolID(); while (this.definitions.has(id) || additions.some((item) => item.id === id)); + do id = newCustomToolID(createId); while (this.definitions.has(id) || additions.some((item) => item.id === id)); additions.push(this.validateDefinition(tool, id, input.namespace, this.revision + 1)); } - for (const definition of additions) this.definitions.set(definition.id, definition); + for (const definition of additions) nextDefinitions.set(definition.id, definition); + const previousDefinitions = this.definitions; + this.definitions = nextDefinitions; try { this.publishDefinitions(this.list(), this.revision + 1); } catch (error) { - for (const definition of additions) this.definitions.delete(definition.id); + this.definitions = previousDefinitions; throw error; } this.revision++; @@ -355,11 +368,6 @@ export class CustomWebMCPRegistry { .sort((a, b) => a.id.localeCompare(b.id)); }; - current = async (): Promise => { - await this.settleReconciliation(); - return this.list(); - }; - private async settleReconciliation(): Promise { try { await this.reconcile(); @@ -415,12 +423,14 @@ export class CustomWebMCPRegistry { if (!toolResult.success) { throw new Error(`invalid MCP tool definition: ${formatToolSchemaError(toolResult.error)}`); } - if (!isRecord(input.tool.outputSchema)) { - throw new Error('tool.outputSchema is required and must be an object'); + if (input.tool.outputSchema !== undefined && !isRecord(input.tool.outputSchema)) { + throw new Error('tool.outputSchema must be an object'); } const inputValidator = compileSchema(input.tool.inputSchema, 'tool.inputSchema'); - const outputValidator = compileSchema(input.tool.outputSchema, 'tool.outputSchema'); + const outputValidator = input.tool.outputSchema === undefined + ? undefined + : compileSchema(input.tool.outputSchema, 'tool.outputSchema'); const pageExecuteSource = input.kind === 'page' ? input.execute.toString() : undefined; if (pageExecuteSource) { try { diff --git a/server/runtime/package-lock.json b/server/runtime/package-lock.json index 54bba6db..b10590d5 100644 --- a/server/runtime/package-lock.json +++ b/server/runtime/package-lock.json @@ -8,6 +8,7 @@ "dependencies": { "@modelcontextprotocol/core": "2.0.0", "@modelcontextprotocol/server": "2.0.0", + "@paralleldrive/cuid2": "3.3.0", "meriyah": "7.3.1", "patchright": "1.62.3", "playwright-core": "1.62.1", @@ -553,6 +554,32 @@ "node": ">=20" } }, + "node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-3.3.0.tgz", + "integrity": "sha512-OqiFvSOF0dBSesELYY2CAMa4YINvlLpvKOz/rv6NeZEqiyttlHgv98Juwv4Ch+GrEV7IZ8jfI2VcEoYUjXXCjw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1", + "bignumber.js": "^9.3.1", + "error-causes": "^3.0.2" + }, + "bin": { + "cuid2": "bin/cuid2.js" + } + }, "node_modules/@types/node": { "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", @@ -563,6 +590,15 @@ "undici-types": "~6.21.0" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -572,6 +608,12 @@ "node": ">=8" } }, + "node_modules/error-causes": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/error-causes/-/error-causes-3.0.2.tgz", + "integrity": "sha512-i0B8zq1dHL6mM85FGoxaJnVtx6LD5nL2v0hlpGdntg5FOSyzQ46c9lmz5qx0xRS2+PWHGOHcYxGIBC5Le2dRMw==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", diff --git a/server/runtime/package.json b/server/runtime/package.json index 70ce00df..70d2f10f 100644 --- a/server/runtime/package.json +++ b/server/runtime/package.json @@ -7,6 +7,7 @@ "dependencies": { "@modelcontextprotocol/core": "2.0.0", "@modelcontextprotocol/server": "2.0.0", + "@paralleldrive/cuid2": "3.3.0", "meriyah": "7.3.1", "patchright": "1.62.3", "playwright-core": "1.62.1", From ce3ee312a7c97b744a498ea7d1b86887f6601250 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:13:36 +0000 Subject: [PATCH 12/18] Wrap WebMCP discovery description --- server/lib/oapi/oapi.go | 12 ++++++------ server/openapi.yaml | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 9a204301..5061b47e 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -26277,12 +26277,12 @@ var swaggerSpec = []string{ "Sv9IjcVUIqIuxLveQEIjAeIWLtFzcVmoa6VvFViuSuAXLr4mYFh+NUitySTEZahtlYd4qMEGsLE1kaig", "gPktW45IhTepSjjqI4gZzbPCsifsF/nc3x/jk4o30sFhTeGqMU71T40SLUeMxdeEis6MnstiXj7nAMp0", "RROqlAkJAlgKLd4kK7sAnjG0KRGbV4Rz+G0EavhSTx5sHV7iy2XTQpQfsbGYctWHdzV/H4TU7bDvWAJv", - "GP4gGgumx7A7vdKux+OBrAMpDma5iHR3ZdrNOcYjFLrfD9XTveoxGy/onqpzoeBOA7jiCybmYwG2Dc07", - "6EWw6rWFyw9mDrxRRLlqwEMMUtIPLxaUSjDXSjptwstUSCJiF3yMMfQB9Btc7JDbhKf9SEGvMCveK1JB", - "uOe++X4oRlmAy/31RUWKsBVQBTwz+Z2wpAUpG8KIcOKWD+d+PrQp68anQuhmZQ7dTMwZJLtBSDepeGH9", - "EbKAWYJ3RnIAwDgsemv4PwsRTzKsN55ymP3QchguuYH6IwWJGmXGD+Njf5I4eIzy09T3g8Gpil6ipnFR", - "0kjKpBuO1Dtb7Z8mYHyKI1pBxm+J06z7F9a+B518gOmpuf9oOUwh2pBrBJa6xFLNNB1w72rg6Xj/4Fq/", - "s+e2icbgS1p/x6Sr6sqDDJktIcc/f/7fAQAA//+MUaKbBa4DAA==", + "GP4gGgumx7A7vdKux+OBrAMpDma5iHR3ZdrNOcYjFLrfD9XTveoxGy/onqpzoeBOA7jiCybmY+FtG0j2", + "wGAN6WZg1msLtx9MHXijiHPVgIsYxKQfniwol2CulXTahKepkEXELvgYg+gD6jf42CG5CY/7kYJuYVq8", + "16SCgM998/1QjNIAqcOBehBGJFLErYAq4J3Jb4UlNUjpEEaEI7d8OfcTok1ZN74VQjcrk+hmYs4g2w1i", + "uknHC+vPkAXMEjw0kgcAxmHRXcP/WYh4lGG98ZjD9IeW03DJD9QfKcjUKFN+GB/7o8TBa5Sfpr4fDE5V", + "dBM1jYuyRlIm3XCk3tlq/zQh41Mg0Qo0fkugZt3BsPZB6OQDTE/N/0fLYQrRBl0jsNQllmrm6YCLVwNR", + "x/sHV/udXbdNPAZf0vw7JmVV1x5kyWyJOf758/8OAAD//4tvtGgGrgMA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index c7517fac..63a017f7 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1360,7 +1360,8 @@ paths: get: summary: Discover WebMCP tools across the browser description: | - Returns a snapshot of WebMCP tools registered by every open tab and by embedded content within those tabs. + Returns a snapshot of WebMCP tools registered by every open tab and by embedded + content within those tabs. On the first request, Kernel starts monitoring the browser. Tabs and windows that are already open are included, and tabs, windows, and embedded content opened later are tracked automatically. From 01c1c472cb383516ca2bd4095577c6198ce771ad Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:18:31 +0000 Subject: [PATCH 13/18] Preserve custom CDP tool results across navigation --- server/cmd/api/api/api.go | 1 + server/cmd/api/api/custom_webmcp.go | 42 ++- server/cmd/api/api/webmcp.go | 15 +- server/cmd/api/api/webmcp_test.go | 7 +- server/docs/repl.md | 2 +- server/e2e/e2e_custom_webmcp_test.go | 132 ++++++++ server/lib/browsersurface/tracker.go | 1 + server/lib/browsersurface/tracker_test.go | 1 + server/lib/browsersurface/types.go | 1 + server/lib/oapi/oapi.go | 354 +++++++++++----------- server/lib/webmcpclient/client.go | 1 + server/lib/webmcpclient/manager.go | 18 ++ server/lib/webmcpclient/types.go | 1 + server/openapi.yaml | 6 +- server/runtime/browser-repl.ts | 44 ++- server/runtime/custom-webmcp.ts | 31 ++ server/runtime/webmcp.ts | 1 + 17 files changed, 476 insertions(+), 182 deletions(-) create mode 100644 server/e2e/e2e_custom_webmcp_test.go diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index d7a528d2..8a6932c0 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -56,6 +56,7 @@ var _ S2Storage = (*events.S2StorageController)(nil) type webMCPClient interface { Tools(ctx context.Context) ([]webmcpclient.Tool, error) + CustomTool(ctx context.Context, toolRef string) (id, targetID string, err error) Invoke(ctx context.Context, toolRef string, input map[string]any) (webmcpclient.InvocationResult, error) Close() error } diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index 9fc4e248..2eb12ed9 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -9,12 +9,15 @@ import ( "github.com/kernel/kernel-images/server/lib/logger" "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/kernel/kernel-images/server/lib/webmcpclient" + "github.com/nrednav/cuid2" ) const ( customWebMCPOperationTimeout = 60 * time.Second maxCustomWebMCPSourceBytes = 8_000_000 maxCustomWebMCPRequestBytes = maxCustomWebMCPSourceBytes + (4 << 10) + maxCustomWebMCPResultBytes = 240 << 10 ) type customWebMCPExecutionError struct { @@ -102,7 +105,42 @@ func (m *browserReplManager) customWebMCPTools(ctx context.Context, code string) return tools, nil } +func (m *browserReplManager) invokeCustomCDPTool(ctx context.Context, id, targetID string, input map[string]any, timeout time.Duration) (webmcpclient.InvocationResult, error) { + invocation := webmcpclient.InvocationResult{InvocationID: cuid2.Generate()} + encodedID, _ := json.Marshal(id) + encodedTarget, _ := json.Marshal(targetID) + encodedInput, err := json.Marshal(input) + if err != nil { + return invocation, err + } + code := fmt.Sprintf(`{ const result = JSON.stringify(await webmcp.invokeCustomCDPTool(%s, %s, %s)); + if (Buffer.byteLength(result) > %d) throw new Error('custom WebMCP output exceeds 240 KiB'); + repl.write(result); }`, encodedID, encodedTarget, encodedInput, maxCustomWebMCPResultBytes) + output, err := m.executeCustomWebMCPCodeWithTimeout(ctx, code, timeout) + if err != nil { + var executionErr *customWebMCPExecutionError + if errors.As(err, &executionErr) { + if executionErr.code == "custom_tool_not_found" { + return invocation, webmcpclient.ErrToolNotFound + } + invocation.Status = "error" + invocation.ErrorText = executionErr.Error() + return invocation, nil + } + return invocation, webmcpclient.ErrOutcomeUnknown + } + if err := json.Unmarshal(output, &invocation.Output); err != nil { + return invocation, fmt.Errorf("decode custom WebMCP invocation: %w", err) + } + invocation.Status = "completed" + return invocation, nil +} + func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code string) (json.RawMessage, error) { + return m.executeCustomWebMCPCodeWithTimeout(ctx, code, customWebMCPOperationTimeout) +} + +func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Context, code string, timeout time.Duration) (json.RawMessage, error) { if err := m.acquire(ctx); err != nil { return nil, err } @@ -118,7 +156,7 @@ func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code s } ctx = operationCtx - request, err := prepareBrowserReplRequest(code, customWebMCPOperationTimeout) + request, err := prepareBrowserReplRequest(code, timeout) if err != nil { return nil, &customWebMCPExecutionError{message: err.Error()} } @@ -126,7 +164,7 @@ func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code s return nil, fmt.Errorf("start Browser REPL: %w", err) } replID := m.child.id - response, err := m.executeLocked(ctx, request, customWebMCPOperationTimeout) + response, err := m.executeLocked(ctx, request, timeout) if err != nil { var notDispatched *browserReplNotDispatchedError if errors.As(err, ¬Dispatched) { diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 81d7ed1f..5c7a5b02 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -83,6 +83,7 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT Id: definition.Id, Namespace: definition.Namespace, } + responseTool.Source.TargetId = nonEmptyString(tool.Source.TargetID) responseTool.Tool = oapi.WebMCPToolMetadata{ Name: definition.Tool.Name, Title: definition.Tool.Title, @@ -124,7 +125,19 @@ func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWe invokeCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - result, err := s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) + customID, targetID, err := s.webmcp.CustomTool(invokeCtx, request.Body.ToolRef) + var result webmcpclient.InvocationResult + if err == nil { + if customID == "" { + result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) + } else if definition, ok := s.browserRepl.customToolsSnapshot()[customID]; !ok { + err = webmcpclient.ErrToolNotFound + } else if definition.Kind == "cdp" { + result, err = s.browserRepl.invokeCustomCDPTool(invokeCtx, customID, targetID, request.Body.Input, timeout) + } else { + result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) + } + } if err != nil { switch { case errors.Is(err, webmcpclient.ErrToolNotFound): diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 7d3f3f3a..f5fdd576 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -26,6 +26,10 @@ func (f *fakeWebMCPClient) Tools(_ context.Context) ([]webmcpclient.Tool, error) return f.tools, f.toolsErr } +func (f *fakeWebMCPClient) CustomTool(_ context.Context, _ string) (string, string, error) { + return "", "", nil +} + func (f *fakeWebMCPClient) Invoke(_ context.Context, toolRef string, input map[string]any) (webmcpclient.InvocationResult, error) { f.toolRef = toolRef f.input = input @@ -94,7 +98,7 @@ func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { }}) client := &fakeWebMCPClient{tools: []webmcpclient.Tool{{ Ref: "wmcp_custom", Name: "fill_payment_form", CustomID: "ct_abcdefghijklmnopqrstuvwx", - Source: webmcpclient.ToolSource{WindowID: 1, TabID: 2, PageTitle: "Checkout", PageURL: "https://checkout.stripe.com/"}, + Source: webmcpclient.ToolSource{WindowID: 1, TabID: 2, TargetID: "target-2", PageTitle: "Checkout", PageURL: "https://checkout.stripe.com/"}, }}} service := &ApiService{webmcp: client, browserRepl: manager} @@ -104,6 +108,7 @@ func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { require.Len(t, tools, 1) require.Equal(t, "stripe.com", tools[0].Source.Custom.Namespace) require.Equal(t, "ct_abcdefghijklmnopqrstuvwx", tools[0].Source.Custom.Id) + require.Equal(t, "target-2", *tools[0].Source.TargetId) require.Equal(t, outputSchema, *tools[0].Tool.OutputSchema) exclude := true diff --git a/server/docs/repl.md b/server/docs/repl.md index 6be15e79..11ac830c 100644 --- a/server/docs/repl.md +++ b/server/docs/repl.md @@ -173,7 +173,7 @@ Every WebMCP request is bound to the active Browser REPL execution and is aborte `GET /webmcp/custom-tools` lists custom tools, `POST /webmcp/custom-tools` atomically adds a namespaced batch from a JavaScript expression, and `DELETE /webmcp/custom-tools/{id}` removes one generated tool ID. The same operations are available through `webmcp.addCustomTools(...)`, `webmcp.listCustomTools()`, and `webmcp.removeCustomTool(id)`. To update one tool, remove its ID and add a replacement. Set `force_overwrite_namespace: true` on POST (or `forceOverwriteNamespace: true` in the REPL) to atomically replace all tools in a namespace. The [REPL-local custom WebMCP registry gist](https://gist.github.com/rgarcia/69f82819ef1b5644964795d11bdc9d2d) is the prior art for the registry and Google Flights example. -Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. CDP handlers receive matching descriptors with `frame_id`, `session_id`, `target_id`, `top_target_id`, and `url`; `target_id` is null for an in-process child frame, whose `session_id` and `frame_id` can be used with raw `cdp()`. A `page` definition executes its self-contained function in the registration document; a `cdp` definition delegates through an isolated-world binding and runs in this Browser REPL with the normal helpers and persistent state. +Definitions match `url_patterns` against every top-level document, nested frame, and out-of-process iframe. A match publishes one native imperative WebMCP registration on that tab's top document. CDP handlers receive matching descriptors with `frame_id`, `session_id`, `target_id`, `top_target_id`, and `url`; `target_id` is null for an in-process child frame, whose `session_id` and `frame_id` can be used with raw `cdp()`. A `page` definition executes its self-contained function in the registration document. A `cdp` definition runs in this Browser REPL with the normal helpers and persistent state. `/webmcp/invoke` executes CDP-backed tools independently of the registration document, so their handlers can navigate that tab and still return a result. Direct invocation through the page's native WebMCP API uses an isolated-world binding and remains subject to the page document's lifetime. CDP-backed `/webmcp/invoke` results over 240 KiB return an error rather than a truncated result. ```js const [readTitle] = await webmcp.addCustomTools({ diff --git a/server/e2e/e2e_custom_webmcp_test.go b/server/e2e/e2e_custom_webmcp_test.go new file mode 100644 index 00000000..29375633 --- /dev/null +++ b/server/e2e/e2e_custom_webmcp_test.go @@ -0,0 +1,132 @@ +package e2e + +import ( + "context" + "fmt" + "net/http" + "os/exec" + "testing" + "time" + + instanceoapi "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCustomWebMCPInvokesAcrossNavigation(t *testing.T) { + if _, err := exec.LookPath("docker"); err != nil { + t.Skipf("docker not available: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + c := NewTestContainer(t, headfulImage) + require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{ + "CHROMIUM_FLAGS": "--enable-features=WebMCPTesting,DevToolsWebMCPSupport", + }})) + defer c.Stop(ctx) + require.NoError(t, c.WaitReady(ctx)) + client, err := c.APIClient() + require.NoError(t, err) + + executeWebMCPPlaywright(t, ctx, client, ` + await page.route('http://127.0.0.1:10001/fixture/**', route => + route.fulfill({contentType: 'text/html', body: 'Flight results
Flight 123
'})); + await page.goto('http://127.0.0.1:10001/fixture/start'); + return page.url(); + `, new(string)) + + added, err := client.AddCustomWebMCPToolsWithResponse(ctx, instanceoapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "navigation.test", + Source: `[{kind:'cdp', match:{url_patterns:['http://127.0.0.1:10001/fixture/*']}, + tool:{name:'search_flights',description:'Search flights.', + inputSchema:{type:'object',properties:{origin:{type:'string'}},required:['origin']}, + outputSchema:{type:'object',properties:{origin:{type:'string'},url:{type:'string'}},required:['origin','url']}}, + execute:async (input,{matches})=>{ + await switchTab(matches[0].top_target_id); + await gotoUrl('http://127.0.0.1:10001/fixture/results'); + await waitForLoad(); + return {origin:input.origin,url:await js(() => location.href)}; + }}]`, + }) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, added.StatusCode(), "%s", added.Body) + + var ref string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, tools.StatusCode()) || tools.JSON200 == nil { + return + } + for _, tool := range tools.JSON200.Tools { + if tool.Tool.Name == "search_flights" { + ref = tool.ToolRef + return + } + } + assert.Fail(collect, "custom tool not discovered") + }, 10*time.Second, 200*time.Millisecond) + + timeout := 10 + result, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: ref, Input: map[string]any{"origin": "SFO"}, TimeoutSec: &timeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, result.StatusCode(), "%s", result.Body) + require.NotNil(t, result.JSON200) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusCompleted, result.JSON200.Status) + require.Equal(t, map[string]any{"origin": "SFO", "url": "http://127.0.0.1:10001/fixture/results"}, result.JSON200.Output) + + tools, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + require.NoError(t, err) + require.Equal(t, http.StatusOK, tools.StatusCode()) + require.NotNil(t, tools.JSON200) + require.Len(t, tools.JSON200.Tools, 1) + invalid, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: tools.JSON200.Tools[0].ToolRef, Input: map[string]any{"origin": 123}, TimeoutSec: &timeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, invalid.StatusCode(), "%s", invalid.Body) + require.NotNil(t, invalid.JSON200) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, invalid.JSON200.Status) + require.Contains(t, *invalid.JSON200.ErrorText, "input failed JSON Schema validation") + + replTimeout := 20 + replResult, err := client.ExecuteBrowserReplWithResponse(ctx, instanceoapi.ExecuteBrowserReplJSONRequestBody{ + Code: fmt.Sprintf(`const result = await webmcp.invokeTool(%q, {origin: 'SFO'}, {timeoutSec: 10}); + if (result.status !== 'completed' || result.output.url !== 'http://127.0.0.1:10001/fixture/results') + throw new Error(JSON.stringify(result)); + repl.write(JSON.stringify(result));`, tools.JSON200.Tools[0].ToolRef), + TimeoutSec: &replTimeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, replResult.StatusCode(), "%s", replResult.Body) + require.NotNil(t, replResult.JSON200) + require.True(t, replResult.JSON200.Success, "%s", replResult.Body) + + oversized, err := client.AddCustomWebMCPToolsWithResponse(ctx, instanceoapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "large-result.test", + Source: `[{kind:'cdp', match:{url_patterns:['http://127.0.0.1:10001/fixture/*']}, + tool:{name:'large_result',description:'Return a large result.',inputSchema:{type:'object'}}, + execute:async ()=>({payload:'x'.repeat(250*1024)})}]`, + }) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, oversized.StatusCode(), "%s", oversized.Body) + tools, err = client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + require.NoError(t, err) + var largeRef string + for _, tool := range tools.JSON200.Tools { + if tool.Tool.Name == "large_result" { + largeRef = tool.ToolRef + } + } + require.NotEmpty(t, largeRef) + large, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: largeRef, Input: map[string]any{}, TimeoutSec: &timeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, large.StatusCode(), "%s", large.Body) + require.NotNil(t, large.JSON200) + require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, large.JSON200.Status) + require.Contains(t, *large.JSON200.ErrorText, "output exceeds 240 KiB") +} diff --git a/server/lib/browsersurface/tracker.go b/server/lib/browsersurface/tracker.go index 897c0672..2e7e6502 100644 --- a/server/lib/browsersurface/tracker.go +++ b/server/lib/browsersurface/tracker.go @@ -317,6 +317,7 @@ func (t *Tracker) Resolve(sessionID, frameID string) (Location, bool) { location := Location{ WindowID: tab.windowID, TabID: tab.id, + TargetID: tab.targetID, PageTitle: tab.title, PageURL: stripFragment(tab.url), } diff --git a/server/lib/browsersurface/tracker_test.go b/server/lib/browsersurface/tracker_test.go index 3ac6dadb..a1af6806 100644 --- a/server/lib/browsersurface/tracker_test.go +++ b/server/lib/browsersurface/tracker_test.go @@ -283,6 +283,7 @@ func TestTrackerMapsBrowserSurfaceAndPublishesLifecycleEvents(t *testing.T) { require.Equal(t, Location{ WindowID: 1, TabID: 1, + TargetID: "page-a", PageTitle: "Store", PageURL: "https://store.example/", Frame: &FrameLocation{ID: 2, URL: "https://bank.example/"}, diff --git a/server/lib/browsersurface/types.go b/server/lib/browsersurface/types.go index d45d3d06..1684632e 100644 --- a/server/lib/browsersurface/types.go +++ b/server/lib/browsersurface/types.go @@ -79,6 +79,7 @@ type FrameLocation struct { type Location struct { WindowID int TabID int + TargetID string PageTitle string PageURL string Frame *FrameLocation diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 5061b47e..4540e8ea 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -7000,6 +7000,9 @@ type WebMCPToolSource struct { // TabId Monotonically increasing identifier for the tab during the current browser process. TabId int `json:"tab_id"` + // TargetId CDP target ID for a custom tool's registration tab; omitted for page-provided tools. + TargetId *string `json:"target_id,omitempty"` + // WindowId Monotonically increasing identifier for the browser window during the current browser process. WindowId int `json:"window_id"` } @@ -26108,181 +26111,182 @@ var swaggerSpec = []string{ "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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 44f6a81c..01425e88 100644 --- a/server/lib/webmcpclient/client.go +++ b/server/lib/webmcpclient/client.go @@ -287,6 +287,7 @@ func (c *connection) toolsSnapshot() []Tool { source := ToolSource{ WindowID: location.WindowID, TabID: location.TabID, + TargetID: location.TargetID, PageTitle: location.PageTitle, PageURL: location.PageURL, } diff --git a/server/lib/webmcpclient/manager.go b/server/lib/webmcpclient/manager.go index dcdb362a..09b86904 100644 --- a/server/lib/webmcpclient/manager.go +++ b/server/lib/webmcpclient/manager.go @@ -54,6 +54,24 @@ func (m *Manager) Tools(ctx context.Context) ([]Tool, error) { return conn.toolsSnapshot(), nil } +func (m *Manager) CustomTool(ctx context.Context, toolRef string) (string, string, error) { + conn, err := m.getConnection(ctx) + if err != nil { + return "", "", err + } + conn.stateMu.RLock() + defer conn.stateMu.RUnlock() + tool, ok := conn.tools[toolRef] + if !ok || !conn.enabledSessions[tool.sessionID] { + return "", "", ErrToolNotFound + } + location, ok := conn.surface.Resolve(tool.sessionID, tool.frameID) + if !ok { + return "", "", ErrToolNotFound + } + return tool.customID, location.TargetID, nil +} + func (m *Manager) Invoke(ctx context.Context, toolRef string, input map[string]any) (InvocationResult, error) { conn, err := m.getConnection(ctx) if err != nil { diff --git a/server/lib/webmcpclient/types.go b/server/lib/webmcpclient/types.go index 848b0087..9f2bcebe 100644 --- a/server/lib/webmcpclient/types.go +++ b/server/lib/webmcpclient/types.go @@ -23,6 +23,7 @@ type Tool struct { type ToolSource struct { WindowID int TabID int + TargetID string PageTitle string PageURL string Frame *ToolFrame diff --git a/server/openapi.yaml b/server/openapi.yaml index 63a017f7..fada9e6e 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -1398,7 +1398,8 @@ paths: description: | Invokes the exact live registration identified by tool_ref. Non-autosubmit declarative form tools return after their fields are populated with an awaiting_submission status. Other tools - wait for a terminal result, including across navigation. If the request times out after invocation + 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. operationId: invokeWebMCPTool x-telemetry-category: control @@ -6501,6 +6502,9 @@ components: type: integer minimum: 1 description: Monotonically increasing identifier for the tab during the current browser process. + target_id: + type: string + description: CDP target ID for a custom tool's registration tab; omitted for page-provided tools. page_title: type: string description: Current title of the top-level page. diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 41ccaef5..3662d49e 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -1,6 +1,7 @@ // Persistent, unrestricted JavaScript daemon owned by the API process. import { AsyncLocalStorage } from 'async_hooks'; +import { randomUUID } from 'crypto'; import { createServer, Socket } from 'net'; import { StringDecoder } from 'string_decoder'; import { unlinkSync, existsSync, renameSync, writeFileSync, promises as fsp } from 'fs'; @@ -11,7 +12,7 @@ import { BrowserHelpers, buildBrowserGlobals } from './browser-helpers'; import { formatBrowserReplHelp } from './browser-repl-help'; import { CellRuntime } from './cell-runtime'; import { CustomWebMCPRegistry } from './custom-webmcp'; -import { createWebMCPClient } from './webmcp'; +import { createWebMCPClient, WebMCPRequestError } from './webmcp'; const SOCKET_PATH = process.env.BROWSER_REPL_SOCKET || '/tmp/browser-repl.sock'; const REPL_ID = process.env.BROWSER_REPL_ID || 'unknown'; @@ -328,9 +329,50 @@ publishCustomTools([], 0); customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); const webmcp = Object.freeze({ ...webmcpClient, + async invokeTool(toolRef: string, input: Record = {}, options: {timeoutSec?: number} = {}) { + const tool = (await webmcpClient.listTools()).find((candidate) => candidate.tool_ref === toolRef); + if (!tool) throw new WebMCPRequestError(404, {message: 'WebMCP tool is no longer available; discover tools again'}); + const id = tool.source.custom?.id; + if (!id || !customToolRegistry.isCDP(id)) { + return webmcpClient.invokeTool(toolRef, input, options); + } + const targetId = tool.source.target_id; + if (!targetId) throw new WebMCPRequestError(404, {message: 'WebMCP tool is no longer available; discover tools again'}); + const timeoutSec = options.timeoutSec ?? 60; + if (!Number.isInteger(timeoutSec) || timeoutSec < 1 || timeoutSec > 120) { + throw new WebMCPRequestError(400, {message: 'timeout_sec must be between 1 and 120'}); + } + const invocationId = randomUUID(); + const controller = new AbortController(); + const parentSignal = webmcpExecution.getStore(); + const signal = parentSignal ? AbortSignal.any([parentSignal, controller.signal]) : controller.signal; + let timer: ReturnType | undefined; + try { + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => { + controller.abort(); + reject(new WebMCPRequestError(504, { + code: 'outcome_unknown', invocation_id: invocationId, + message: 'the invocation started, but its final outcome could not be observed; do not retry automatically', + })); + }, timeoutSec * 1000); + }); + const output = await Promise.race([customToolRegistry.invokeCDP(id, targetId, input, signal), timeout]); + return {invocation_id: invocationId, status: 'completed' as const, output}; + } catch (error) { + if (error instanceof WebMCPRequestError) throw error; + if (error instanceof Error && 'code' in error && error.code === 'custom_tool_not_found') { + throw new WebMCPRequestError(404, {message: 'WebMCP tool is no longer available; discover tools again'}); + } + return {invocation_id: invocationId, status: 'error' as const, error_text: error instanceof Error ? error.message : String(error)}; + } finally { + if (timer) clearTimeout(timer); + } + }, addCustomTools: customToolRegistry.add, listCustomTools: customToolRegistry.list, removeCustomTool: customToolRegistry.remove, + invokeCustomCDPTool: customToolRegistry.invokeCDP, }); const browserGlobals = buildBrowserGlobals(helpers); const browserNamespace = Object.freeze({ diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index a914d655..d461f758 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -153,6 +153,10 @@ class CustomToolConflictError extends Error { } } +class CustomToolNotFoundError extends Error { + readonly code = 'custom_tool_not_found'; +} + function errorMessage(error: unknown): string { const message = error instanceof Error ? error.message : String(error); if (Buffer.byteLength(message) <= MAX_ERROR_BYTES) return message; @@ -368,6 +372,33 @@ export class CustomWebMCPRegistry { .sort((a, b) => a.id.localeCompare(b.id)); }; + isCDP = (id: string): boolean => this.definitions.get(id)?.kind === 'cdp'; + + invokeCDP = async (id: string, targetId: string, input: Record, signal?: AbortSignal): Promise => { + await this.reconcile(); + const definition = this.definitions.get(id); + const matches = this.pages.get(targetId)?.matches.get(id); + if (definition?.kind !== 'cdp' || !matches?.length) { + throw new CustomToolNotFoundError('custom tool is no longer available; discover tools again'); + } + const inputResult = definition.inputValidator(input); + if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); + const executionSignal = signal ?? new AbortController().signal; + const output = await this.runInvocation(executionSignal, async () => definition.execute( + inputResult.data as Record, + {signal: executionSignal, matches: clone(matches)}, + )); + if (definition.outputValidator) { + const outputResult = definition.outputValidator(output); + if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); + } + const result = output ?? null; + if (Buffer.byteLength(JSON.stringify(result)) > MAX_OUTPUT_BYTES) { + throw new Error('custom WebMCP output exceeds 1 MiB'); + } + return result; + }; + private async settleReconciliation(): Promise { try { await this.reconcile(); diff --git a/server/runtime/webmcp.ts b/server/runtime/webmcp.ts index acdde331..44bce957 100644 --- a/server/runtime/webmcp.ts +++ b/server/runtime/webmcp.ts @@ -11,6 +11,7 @@ export interface WebMCPCustomToolSource { export interface WebMCPToolSource { window_id: number; tab_id: number; + target_id?: string; page_title: string; page_url: string; frame: WebMCPToolFrame | null; From 09bf83d99043765ac163357598a4b6c790097c0f Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:29:24 +0000 Subject: [PATCH 14/18] Classify pre-dispatch failures and bound REPL tool deadlines --- server/cmd/api/api/custom_webmcp.go | 12 ++-- server/cmd/api/api/custom_webmcp_test.go | 13 ++++ server/e2e/e2e_custom_webmcp_test.go | 77 +++++++++++++++++------- server/e2e/e2e_playwright_test.go | 3 + server/runtime/browser-repl.ts | 6 +- 5 files changed, 83 insertions(+), 28 deletions(-) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index 2eb12ed9..42275f7d 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -127,6 +127,10 @@ func (m *browserReplManager) invokeCustomCDPTool(ctx context.Context, id, target invocation.ErrorText = executionErr.Error() return invocation, nil } + var notDispatched *browserReplNotDispatchedError + if errors.As(err, ¬Dispatched) { + return invocation, notDispatched.cause + } return invocation, webmcpclient.ErrOutcomeUnknown } if err := json.Unmarshal(output, &invocation.Output); err != nil { @@ -142,7 +146,7 @@ func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code s func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Context, code string, timeout time.Duration) (json.RawMessage, error) { if err := m.acquire(ctx); err != nil { - return nil, err + return nil, &browserReplNotDispatchedError{cause: err} } defer m.release() @@ -152,7 +156,7 @@ func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Cont cancelOperation(nil) }() if err := context.Cause(operationCtx); err != nil { - return nil, err + return nil, &browserReplNotDispatchedError{cause: err} } ctx = operationCtx @@ -161,14 +165,14 @@ func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Cont return nil, &customWebMCPExecutionError{message: err.Error()} } if err := m.ensureLocked(ctx); err != nil { - return nil, fmt.Errorf("start Browser REPL: %w", err) + return nil, &browserReplNotDispatchedError{cause: fmt.Errorf("start Browser REPL: %w", err)} } replID := m.child.id response, err := m.executeLocked(ctx, request, timeout) if err != nil { var notDispatched *browserReplNotDispatchedError if errors.As(err, ¬Dispatched) { - return nil, notDispatched.cause + return nil, notDispatched } var timeoutErr *browserReplTimeoutError if errors.As(err, &timeoutErr) { diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 152ea22c..00f00efc 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/kernel/kernel-images/server/lib/oapi" + "github.com/kernel/kernel-images/server/lib/webmcpclient" "github.com/stretchr/testify/require" ) @@ -32,6 +33,18 @@ const customWebMCPTestSource = `[ }, ]` +func TestCustomCDPInvocationNotDispatchedWhileReplIsBusy(t *testing.T) { + manager := newBrowserReplManager() + require.NoError(t, manager.acquire(context.Background())) + defer manager.release() + + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) + defer cancel() + _, err := manager.invokeCustomCDPTool(ctx, "ct_test", "target", map[string]any{}, time.Second) + require.ErrorIs(t, err, context.DeadlineExceeded) + require.NotErrorIs(t, err, webmcpclient.ErrOutcomeUnknown) +} + func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() diff --git a/server/e2e/e2e_custom_webmcp_test.go b/server/e2e/e2e_custom_webmcp_test.go index 29375633..96ae6eb9 100644 --- a/server/e2e/e2e_custom_webmcp_test.go +++ b/server/e2e/e2e_custom_webmcp_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "os/exec" "testing" "time" @@ -13,21 +12,8 @@ import ( "github.com/stretchr/testify/require" ) -func TestCustomWebMCPInvokesAcrossNavigation(t *testing.T) { - if _, err := exec.LookPath("docker"); err != nil { - t.Skipf("docker not available: %v", err) - } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancel() - - c := NewTestContainer(t, headfulImage) - require.NoError(t, c.Start(ctx, ContainerConfig{Env: map[string]string{ - "CHROMIUM_FLAGS": "--enable-features=WebMCPTesting,DevToolsWebMCPSupport", - }})) - defer c.Stop(ctx) - require.NoError(t, c.WaitReady(ctx)) - client, err := c.APIClient() - require.NoError(t, err) +func testCustomWebMCPInvokesAcrossNavigation(t *testing.T, ctx context.Context, client *instanceoapi.ClientWithResponses) { + t.Helper() executeWebMCPPlaywright(t, ctx, client, ` await page.route('http://127.0.0.1:10001/fixture/**', route => @@ -112,15 +98,20 @@ func TestCustomWebMCPInvokesAcrossNavigation(t *testing.T) { }) require.NoError(t, err) require.Equal(t, http.StatusCreated, oversized.StatusCode(), "%s", oversized.Body) - tools, err = client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) - require.NoError(t, err) var largeRef string - for _, tool := range tools.JSON200.Tools { - if tool.Tool.Name == "large_result" { - largeRef = tool.ToolRef + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, tools.StatusCode()) || tools.JSON200 == nil { + return } - } - require.NotEmpty(t, largeRef) + for _, tool := range tools.JSON200.Tools { + if tool.Tool.Name == "large_result" { + largeRef = tool.ToolRef + return + } + } + assert.Fail(collect, "large result tool not discovered") + }, 10*time.Second, 200*time.Millisecond) large, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ ToolRef: largeRef, Input: map[string]any{}, TimeoutSec: &timeout, }) @@ -129,4 +120,44 @@ func TestCustomWebMCPInvokesAcrossNavigation(t *testing.T) { require.NotNil(t, large.JSON200) require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, large.JSON200.Status) require.Contains(t, *large.JSON200.ErrorText, "output exceeds 240 KiB") + + slow, err := client.AddCustomWebMCPToolsWithResponse(ctx, instanceoapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "deadline.test", + Source: `[{kind:'cdp', match:{url_patterns:['http://127.0.0.1:10001/fixture/*']}, + tool:{name:'slow_result',description:'Wait before returning.',inputSchema:{type:'object'}}, + execute:async ()=>{await waitMs(3500);return {done:true}}}]`, + }) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, slow.StatusCode(), "%s", slow.Body) + var slowRef string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, tools.StatusCode()) || tools.JSON200 == nil { + return + } + for _, tool := range tools.JSON200.Tools { + if tool.Tool.Name == "slow_result" { + slowRef = tool.ToolRef + return + } + } + assert.Fail(collect, "slow result tool not discovered") + }, 10*time.Second, 200*time.Millisecond) + + shortTimeout := 3 + deadline, err := client.ExecuteBrowserReplWithResponse(ctx, instanceoapi.ExecuteBrowserReplJSONRequestBody{ + Code: fmt.Sprintf(`try { + await webmcp.invokeTool(%q, {}, {timeoutSec:30}); + throw new Error('expected a deadline error'); + } catch (error) { + if (error.code !== 'outcome_unknown') throw error; + repl.write('deadline guarded'); + }`, slowRef), + TimeoutSec: &shortTimeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, deadline.StatusCode(), "%s", deadline.Body) + require.NotNil(t, deadline.JSON200) + require.True(t, deadline.JSON200.Success, "%s", deadline.Body) + require.Nil(t, deadline.JSON200.ReplTerminated) } diff --git a/server/e2e/e2e_playwright_test.go b/server/e2e/e2e_playwright_test.go index a88f6213..a1fb61bd 100644 --- a/server/e2e/e2e_playwright_test.go +++ b/server/e2e/e2e_playwright_test.go @@ -262,6 +262,9 @@ func TestPlaywrightExecuteAPI(t *testing.T) { t.Run("WebMCPDeclarative", func(t *testing.T) { testWebMCPDeclarative(t, ctx, client) }) + t.Run("WebMCPCustomNavigation", func(t *testing.T) { + testCustomWebMCPInvokesAcrossNavigation(t, ctx, client) + }) } func TestPlaywrightExecuteTimeoutReturnsPromptlyAndRecovers(t *testing.T) { diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 3662d49e..7d4e4064 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -342,6 +342,10 @@ const webmcp = Object.freeze({ if (!Number.isInteger(timeoutSec) || timeoutSec < 1 || timeoutSec > 120) { throw new WebMCPRequestError(400, {message: 'timeout_sec must be between 1 and 120'}); } + const remainingMs = helpers.executionDeadlineMs === null + ? Infinity + : helpers.executionDeadlineMs - WEBMCP_DEADLINE_MARGIN_MS - Date.now(); + if (remainingMs <= 0) throw new Error('WebMCP request exceeded the Browser REPL execution deadline'); const invocationId = randomUUID(); const controller = new AbortController(); const parentSignal = webmcpExecution.getStore(); @@ -355,7 +359,7 @@ const webmcp = Object.freeze({ code: 'outcome_unknown', invocation_id: invocationId, message: 'the invocation started, but its final outcome could not be observed; do not retry automatically', })); - }, timeoutSec * 1000); + }, Math.min(timeoutSec * 1000, remainingMs)); }); const output = await Promise.race([customToolRegistry.invokeCDP(id, targetId, input, signal), timeout]); return {invocation_id: invocationId, status: 'completed' as const, output}; From b93a0929e8cb0df2e4063910a094f37586878fa5 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:39:58 +0000 Subject: [PATCH 15/18] Recover custom tool registrations and simplify state ownership --- server/cmd/api/api/browser_repl.go | 93 ++- server/cmd/api/api/custom_webmcp_test.go | 40 +- server/cmd/api/api/webmcp.go | 23 +- server/cmd/api/api/webmcp_test.go | 42 +- server/e2e/e2e_custom_webmcp_test.go | 65 ++ server/runtime/browser-repl.ts | 17 +- server/runtime/custom-webmcp-definitions.ts | 322 ++++++++++ server/runtime/custom-webmcp-page-runtime.ts | 267 ++++++++ server/runtime/custom-webmcp.ts | 630 ++----------------- server/runtime/tsconfig.json | 1 + 10 files changed, 831 insertions(+), 669 deletions(-) create mode 100644 server/runtime/custom-webmcp-definitions.ts create mode 100644 server/runtime/custom-webmcp-page-runtime.ts diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 43d863b4..830b3a88 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -75,14 +75,12 @@ type browserReplChild struct { // browserReplManager owns execution admission and the persistent Node child. // Lifecycle synchronization stays behind its Execute and Shutdown methods. type browserReplManager struct { - admission chan struct{} - lifecycle context.Context - stop context.CancelCauseFunc - child *browserReplChild // guarded by admission - customToolsMu sync.RWMutex - customToolsReplID string - customToolsRevision int - customTools map[string]oapi.CustomWebMCPDefinition + admission chan struct{} + lifecycle context.Context + stop context.CancelCauseFunc + child *browserReplChild // guarded by admission + customToolsMu sync.RWMutex + customToolsReplID string } func newBrowserReplManager() *browserReplManager { @@ -90,63 +88,48 @@ func newBrowserReplManager() *browserReplManager { admission := make(chan struct{}, 1) admission <- struct{}{} return &browserReplManager{ - admission: admission, - lifecycle: lifecycle, - stop: stop, - customTools: make(map[string]oapi.CustomWebMCPDefinition), + admission: admission, + lifecycle: lifecycle, + stop: stop, } } -// replaceCustomToolsSnapshot refreshes the Go-side discovery cache from the daemon's -// complete, atomically published snapshot. Adding tools never replaces the registry. -func (m *browserReplManager) replaceCustomToolsSnapshot(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { +func (m *browserReplManager) setCustomToolsReplID(replID string) { m.customToolsMu.Lock() defer m.customToolsMu.Unlock() m.customToolsReplID = replID - m.customToolsRevision = revision - m.replaceCustomToolsLocked(tools) } -func (m *browserReplManager) refreshCustomToolsSnapshot(replID string, revision int, tools []oapi.CustomWebMCPDefinition) { - m.customToolsMu.Lock() - defer m.customToolsMu.Unlock() - if m.customToolsReplID != replID || revision < m.customToolsRevision { - return +// The daemon's atomically published file is the sole discovery snapshot. A +// missing or unreadable snapshot is an error, not permission to serve stale metadata. +func (m *browserReplManager) customToolsSnapshot() (map[string]oapi.CustomWebMCPDefinition, error) { + m.customToolsMu.RLock() + defer m.customToolsMu.RUnlock() + if m.customToolsReplID == "" { + return make(map[string]oapi.CustomWebMCPDefinition), nil } - m.customToolsRevision = revision - m.replaceCustomToolsLocked(tools) -} - -func (m *browserReplManager) replaceCustomToolsLocked(tools []oapi.CustomWebMCPDefinition) { - m.customTools = make(map[string]oapi.CustomWebMCPDefinition, len(tools)) - for _, tool := range tools { - m.customTools[tool.Id] = tool + data, err := os.ReadFile(browserReplCustomToolsPath()) + if err != nil { + return nil, fmt.Errorf("read custom WebMCP discovery snapshot: %w", err) } -} - -func (m *browserReplManager) customToolsSnapshot() map[string]oapi.CustomWebMCPDefinition { - m.customToolsMu.RLock() - replID := m.customToolsReplID - m.customToolsMu.RUnlock() - if replID != "" { - if data, err := os.ReadFile(browserReplCustomToolsPath()); err == nil { - var state struct { - ReplID string `json:"repl_id"` - Revision int `json:"revision"` - Tools []oapi.CustomWebMCPDefinition `json:"tools"` - } - if json.Unmarshal(data, &state) == nil && state.ReplID == replID { - m.refreshCustomToolsSnapshot(replID, state.Revision, state.Tools) - } - } + var state struct { + ReplID string `json:"repl_id"` + Tools []oapi.CustomWebMCPDefinition `json:"tools"` } - m.customToolsMu.RLock() - defer m.customToolsMu.RUnlock() - tools := make(map[string]oapi.CustomWebMCPDefinition, len(m.customTools)) - for id, tool := range m.customTools { - tools[id] = tool + if err := json.Unmarshal(data, &state); err != nil { + return nil, fmt.Errorf("decode custom WebMCP discovery snapshot: %w", err) + } + if state.ReplID != m.customToolsReplID { + return nil, fmt.Errorf("custom WebMCP discovery snapshot belongs to another REPL") + } + if state.Tools == nil { + return nil, fmt.Errorf("custom WebMCP discovery snapshot has no tools list") + } + tools := make(map[string]oapi.CustomWebMCPDefinition, len(state.Tools)) + for _, tool := range state.Tools { + tools[tool.Id] = tool } - return tools + return tools, nil } func browserReplCustomToolsPath() string { @@ -268,7 +251,7 @@ func closedWaitChannel(err error) chan error { func (m *browserReplManager) clearLocked(ctx context.Context, child *browserReplChild) { if m.child == child { m.child = nil - m.replaceCustomToolsSnapshot("", 0, nil) + m.setCustomToolsReplID("") _ = os.Remove(browserReplCustomToolsPath()) } removeBrowserReplSocket(logger.FromContext(ctx), browserReplSocketPath()) @@ -311,7 +294,7 @@ func (m *browserReplManager) startLocked(ctx context.Context) error { child.done <- cmd.Wait() }() m.child = child - m.replaceCustomToolsSnapshot(replID, 0, nil) + m.setCustomToolsReplID(replID) deadline := time.Now().Add(browserReplStartupTimeout) for { diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 00f00efc..2f9cc2c9 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -33,6 +33,13 @@ const customWebMCPTestSource = `[ }, ]` +func requireCustomToolsSnapshot(t *testing.T, manager *browserReplManager) map[string]oapi.CustomWebMCPDefinition { + t.Helper() + tools, err := manager.customToolsSnapshot() + require.NoError(t, err) + return tools +} + func TestCustomCDPInvocationNotDispatchedWhileReplIsBusy(t *testing.T) { manager := newBrowserReplManager() require.NoError(t, manager.acquire(context.Background())) @@ -63,7 +70,7 @@ func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { require.Equal(t, "cdp", body.Tools[0].Kind) require.Equal(t, "read_title", body.Tools[0].Tool.Name) require.Equal(t, []string{"https://example.com/*"}, body.Tools[0].Match.UrlPatterns) - require.Equal(t, body.Tools[0], svc.browserRepl.customToolsSnapshot()[body.Tools[0].Id]) + require.Equal(t, body.Tools[0], requireCustomToolsSnapshot(t, svc.browserRepl)[body.Tools[0].Id]) listed, err := svc.ListCustomWebMCPTools(ctx, oapi.ListCustomWebMCPToolsRequestObject{}) require.NoError(t, err) @@ -111,14 +118,27 @@ func TestCustomWebMCPSnapshotRejectsDifferentRepl(t *testing.T) { }) require.NoError(t, err) tool := added.(oapi.AddCustomWebMCPTools201JSONResponse).Tools[0] - require.Contains(t, svc.browserRepl.customToolsSnapshot(), tool.Id) + require.Contains(t, requireCustomToolsSnapshot(t, svc.browserRepl), tool.Id) require.NoError(t, os.WriteFile( browserReplCustomToolsPath(), []byte(`{"repl_id":"different-repl","tools":[]}`), 0o600, )) - require.Contains(t, svc.browserRepl.customToolsSnapshot(), tool.Id) + _, err = svc.browserRepl.customToolsSnapshot() + require.ErrorContains(t, err, "another REPL") + + require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), []byte(`{invalid`), 0o600)) + _, err = svc.browserRepl.customToolsSnapshot() + require.ErrorContains(t, err, "decode custom WebMCP discovery snapshot") + + require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), []byte(`{"repl_id":"`+svc.browserRepl.customToolsReplID+`"}`), 0o600)) + _, err = svc.browserRepl.customToolsSnapshot() + require.ErrorContains(t, err, "no tools list") + + require.NoError(t, os.Remove(browserReplCustomToolsPath())) + _, err = svc.browserRepl.customToolsSnapshot() + require.ErrorIs(t, err, os.ErrNotExist) } func TestCustomWebMCPMetadataIsVisibleBeforeReplCellCompletes(t *testing.T) { @@ -142,7 +162,11 @@ func TestCustomWebMCPMetadataIsVisibleBeforeReplCellCompletes(t *testing.T) { }() require.Eventually(t, func() bool { - for _, tool := range svc.browserRepl.customToolsSnapshot() { + tools, err := svc.browserRepl.customToolsSnapshot() + if err != nil { + return false + } + for _, tool := range tools { if tool.Namespace == "midcell.example" { return true } @@ -198,8 +222,8 @@ func TestCustomWebMCPForceOverwriteNamespace(t *testing.T) { // Invalid replacement must not delete either namespace. _, ok := add("example.com", `[{kind: "cdp"}]`, true).(oapi.AddCustomWebMCPTools400JSONResponse) require.True(t, ok) - require.Contains(t, svc.browserRepl.customToolsSnapshot(), original.Id) - require.Contains(t, svc.browserRepl.customToolsSnapshot(), second.Id) + require.Contains(t, requireCustomToolsSnapshot(t, svc.browserRepl), original.Id) + require.Contains(t, requireCustomToolsSnapshot(t, svc.browserRepl), second.Id) const noOutputSchema = `[{kind: "cdp", match: {url_patterns: ["https://example.com/*"]}, tool: {name: "read_title", description: "Read title", inputSchema: {type: "object"}}, @@ -217,8 +241,8 @@ func TestCustomWebMCPForceOverwriteNamespace(t *testing.T) { listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools[0].Id, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools[1].Id, }) - require.NotContains(t, svc.browserRepl.customToolsSnapshot(), original.Id) - require.NotContains(t, svc.browserRepl.customToolsSnapshot(), second.Id) + require.NotContains(t, requireCustomToolsSnapshot(t, svc.browserRepl), original.Id) + require.NotContains(t, requireCustomToolsSnapshot(t, svc.browserRepl), second.Id) } func TestCustomWebMCPInvalidAdditionPreservesTools(t *testing.T) { diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index 5c7a5b02..a169992a 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -32,8 +32,12 @@ func (s *ApiService) GetWebMCPTools(ctx context.Context, request oapi.GetWebMCPT excludeCustom := request.Params.ExcludeCustom != nil && *request.Params.ExcludeCustom customDefinitions := make(map[string]oapi.CustomWebMCPDefinition) - if s.browserRepl != nil { - customDefinitions = s.browserRepl.customToolsSnapshot() + if !excludeCustom && s.browserRepl != nil { + customDefinitions, err = s.browserRepl.customToolsSnapshot() + if err != nil { + logger.FromContext(ctx).Error("failed to read custom WebMCP discovery snapshot", "err", err) + return oapi.GetWebMCPTools500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to discover WebMCP tools"}}, nil + } } responseTools := make([]oapi.WebMCPTool, 0, len(tools)) @@ -130,12 +134,19 @@ func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWe if err == nil { if customID == "" { result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) - } else if definition, ok := s.browserRepl.customToolsSnapshot()[customID]; !ok { + } else if s.browserRepl == nil { err = webmcpclient.ErrToolNotFound - } else if definition.Kind == "cdp" { - result, err = s.browserRepl.invokeCustomCDPTool(invokeCtx, customID, targetID, request.Body.Input, timeout) } else { - result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) + definitions, snapshotErr := s.browserRepl.customToolsSnapshot() + if snapshotErr != nil { + err = snapshotErr + } else if definition, ok := definitions[customID]; !ok { + err = webmcpclient.ErrToolNotFound + } else if definition.Kind == "cdp" { + result, err = s.browserRepl.invokeCustomCDPTool(invokeCtx, customID, targetID, request.Body.Input, timeout) + } else { + result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) + } } } if err != nil { diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index f5fdd576..8055b61e 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" + "os" + "path/filepath" "strings" "testing" @@ -69,24 +71,40 @@ func TestGetWebMCPToolsMapsRegistrationContext(t *testing.T) { require.True(t, *tool.Tool.Annotations.ConsequentialHint) } -func TestCustomToolCacheRejectsStaleGenerationAndRevision(t *testing.T) { - manager := newBrowserReplManager() - current := oapi.CustomWebMCPDefinition{Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "current"} - stale := oapi.CustomWebMCPDefinition{Id: "ct_zyxwvutsrqponmlkjihgfedc", Namespace: "stale"} - manager.replaceCustomToolsSnapshot("current-repl", 2, []oapi.CustomWebMCPDefinition{current}) +func writeCustomToolsSnapshot(t *testing.T, manager *browserReplManager, tools []oapi.CustomWebMCPDefinition) { + t.Helper() + t.Setenv("BROWSER_REPL_SOCKET", filepath.Join(t.TempDir(), "browser-repl.sock")) + manager.setCustomToolsReplID("test-repl") + data, err := json.Marshal(struct { + ReplID string `json:"repl_id"` + Tools []oapi.CustomWebMCPDefinition `json:"tools"` + }{ReplID: "test-repl", Tools: tools}) + require.NoError(t, err) + require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), data, 0o600)) +} - manager.refreshCustomToolsSnapshot("old-repl", 3, []oapi.CustomWebMCPDefinition{stale}) - manager.refreshCustomToolsSnapshot("current-repl", 1, []oapi.CustomWebMCPDefinition{stale}) +func TestGetWebMCPToolsRejectsUnreadableSnapshot(t *testing.T) { + manager := newBrowserReplManager() + writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{Id: "ct_abcdefghijklmnopqrstuvwx"}}) + service := &ApiService{webmcp: &fakeWebMCPClient{tools: []webmcpclient.Tool{{Ref: "native", Name: "search"}}}, browserRepl: manager} + require.NoError(t, os.WriteFile(browserReplCustomToolsPath(), []byte(`{invalid`), 0o600)) + response, err := service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{}) + require.NoError(t, err) + _, ok := response.(oapi.GetWebMCPTools500JSONResponse) + require.True(t, ok, "expected snapshot failure, got %T", response) - tools := manager.customToolsSnapshot() - require.Contains(t, tools, current.Id) - require.NotContains(t, tools, stale.Id) + exclude := true + response, err = service.GetWebMCPTools(context.Background(), oapi.GetWebMCPToolsRequestObject{ + Params: oapi.GetWebMCPToolsParams{ExcludeCustom: &exclude}, + }) + require.NoError(t, err) + require.Len(t, response.(oapi.GetWebMCPTools200JSONResponse).Tools, 1) } func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { outputSchema := map[string]any{"type": "object"} manager := newBrowserReplManager() - manager.replaceCustomToolsSnapshot("test-repl", 1, []oapi.CustomWebMCPDefinition{{ + writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{ Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "stripe.com", Kind: "cdp", @@ -121,7 +139,7 @@ func TestGetWebMCPToolsAddsCustomMetadataAndFiltersCustomTools(t *testing.T) { func TestGetWebMCPToolsOmitsOptionalCustomOutputSchema(t *testing.T) { manager := newBrowserReplManager() - manager.replaceCustomToolsSnapshot("test-repl", 1, []oapi.CustomWebMCPDefinition{{ + writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{ Id: "ct_abcdefghijklmnopqrstuvwx", Namespace: "example.com", Kind: "cdp", Tool: oapi.WebMCPToolMetadata{ Name: "read_title", Description: "Read a title", InputSchema: map[string]any{"type": "object"}, diff --git a/server/e2e/e2e_custom_webmcp_test.go b/server/e2e/e2e_custom_webmcp_test.go index 96ae6eb9..80012aa1 100644 --- a/server/e2e/e2e_custom_webmcp_test.go +++ b/server/e2e/e2e_custom_webmcp_test.go @@ -121,6 +121,71 @@ func testCustomWebMCPInvokesAcrossNavigation(t *testing.T, ctx context.Context, require.Equal(t, instanceoapi.WebMCPInvocationResultStatusError, large.JSON200.Status) require.Contains(t, *large.JSON200.ErrorText, "output exceeds 240 KiB") + require.NotNil(t, tools.JSON200.Tools[0].Source.TargetId) + targetID := *tools.JSON200.Tools[0].Source.TargetId + patched, err := client.ExecuteBrowserReplWithResponse(ctx, instanceoapi.ExecuteBrowserReplJSONRequestBody{ + Code: fmt.Sprintf(`await js(() => { + const prototype = Object.getPrototypeOf(document.modelContext); + const original = prototype.registerTool; + window.__allowCustomRegistration = false; + Object.defineProperty(prototype, 'registerTool', {configurable: true, value: function(...args) { + if (!window.__allowCustomRegistration) throw new Error('registration temporarily unavailable'); + return original.apply(this, args); + }}); + }, {targetId: %q});`, targetID), + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, patched.StatusCode(), "%s", patched.Body) + require.True(t, patched.JSON200.Success, "%s", patched.Body) + failed, err := client.AddCustomWebMCPToolsWithResponse(ctx, instanceoapi.AddCustomWebMCPToolsJSONRequestBody{ + Namespace: "recovery.test", + Source: `[{kind:'page', match:{url_patterns:['http://127.0.0.1:10001/fixture/*']}, + tool:{name:'recover_registration',description:'Return the input.',inputSchema:{type:'object'}}, + execute:async input=>input}]`, + }) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, failed.StatusCode(), "%s", failed.Body) + beforeRecovery, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + require.NoError(t, err) + for _, tool := range beforeRecovery.JSON200.Tools { + require.NotEqual(t, "recover_registration", tool.Tool.Name) + } + restored, err := client.ExecuteBrowserReplWithResponse(ctx, instanceoapi.ExecuteBrowserReplJSONRequestBody{ + Code: fmt.Sprintf(`await js(() => { window.__allowCustomRegistration = true; }, {targetId: %q});`, targetID), + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, restored.StatusCode(), "%s", restored.Body) + require.True(t, restored.JSON200.Success, "%s", restored.Body) + var recoveredRef string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + tools, err := client.GetWebMCPToolsWithResponse(ctx, &instanceoapi.GetWebMCPToolsParams{}) + if !assert.NoError(collect, err) || !assert.Equal(collect, http.StatusOK, tools.StatusCode()) || tools.JSON200 == nil { + return + } + for _, tool := range tools.JSON200.Tools { + if tool.Tool.Name == "recover_registration" { + recoveredRef = tool.ToolRef + return + } + } + assert.Fail(collect, "registration did not recover in the same document") + }, 10*time.Second, 200*time.Millisecond) + recovered, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: recoveredRef, Input: map[string]any{"origin": "SFO"}, TimeoutSec: &timeout, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, recovered.StatusCode(), "%s", recovered.Body) + require.Equal(t, map[string]any{"origin": "SFO"}, recovered.JSON200.Output) + + pageResult, err := client.ExecuteBrowserReplWithResponse(ctx, instanceoapi.ExecuteBrowserReplJSONRequestBody{ + Code: `const pageTool = (await webmcp.listTools()).find(tool => tool.tool.name === 'recover_registration'); + const nativeResult = await webmcp.invokeTool(pageTool.tool_ref, {origin: 'SFO'}); + if (nativeResult.status !== 'completed' || nativeResult.output.origin !== 'SFO') throw new Error(JSON.stringify(nativeResult));`, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, pageResult.StatusCode(), "%s", pageResult.Body) + require.True(t, pageResult.JSON200.Success, "%s", pageResult.Body) + slow, err := client.AddCustomWebMCPToolsWithResponse(ctx, instanceoapi.AddCustomWebMCPToolsJSONRequestBody{ Namespace: "deadline.test", Source: `[{kind:'cdp', match:{url_patterns:['http://127.0.0.1:10001/fixture/*']}, diff --git a/server/runtime/browser-repl.ts b/server/runtime/browser-repl.ts index 7d4e4064..06c219c2 100644 --- a/server/runtime/browser-repl.ts +++ b/server/runtime/browser-repl.ts @@ -315,9 +315,9 @@ const webmcpClient = createWebMCPClient({ return AbortSignal.any([executionSignal, AbortSignal.timeout(remainingMs)]); }, }); -const publishCustomTools = (tools: ReturnType, revision: number) => { +const publishCustomTools = (tools: ReturnType) => { const temporaryPath = `${CUSTOM_TOOLS_STATE_PATH}.${process.pid}.tmp`; - writeFileSync(temporaryPath, safeStringify({repl_id: REPL_ID, revision, tools}), {mode: 0o600}); + writeFileSync(temporaryPath, safeStringify({repl_id: REPL_ID, tools}), {mode: 0o600}); renameSync(temporaryPath, CUSTOM_TOOLS_STATE_PATH); }; const customToolRegistry = new CustomWebMCPRegistry( @@ -325,15 +325,26 @@ const customToolRegistry = new CustomWebMCPRegistry( (signal, callback) => webmcpExecution.run(signal, callback), publishCustomTools, ); -publishCustomTools([], 0); +publishCustomTools([]); customToolRegistry.setErrorHandler((message) => process.stderr.write(`[custom-webmcp] ${message}\n`)); +let nativeToolRefs = new Set(); const webmcp = Object.freeze({ ...webmcpClient, + async listTools(options: {excludeCustom?: boolean} = {}) { + const tools = await webmcpClient.listTools(options); + nativeToolRefs = new Set(tools.filter((tool) => !tool.source.custom || !customToolRegistry.isCDP(tool.source.custom.id)) + .map((tool) => tool.tool_ref)); + return tools; + }, async invokeTool(toolRef: string, input: Record = {}, options: {timeoutSec?: number} = {}) { + if (nativeToolRefs.has(toolRef) || !customToolRegistry.hasCDP()) { + return webmcpClient.invokeTool(toolRef, input, options); + } const tool = (await webmcpClient.listTools()).find((candidate) => candidate.tool_ref === toolRef); if (!tool) throw new WebMCPRequestError(404, {message: 'WebMCP tool is no longer available; discover tools again'}); const id = tool.source.custom?.id; if (!id || !customToolRegistry.isCDP(id)) { + nativeToolRefs.add(toolRef); return webmcpClient.invokeTool(toolRef, input, options); } const targetId = tool.source.target_id; diff --git a/server/runtime/custom-webmcp-definitions.ts b/server/runtime/custom-webmcp-definitions.ts new file mode 100644 index 00000000..54db6024 --- /dev/null +++ b/server/runtime/custom-webmcp-definitions.ts @@ -0,0 +1,322 @@ +import { ToolSchema } from '@modelcontextprotocol/core'; +import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv'; + +export const MAX_OUTPUT_BYTES = 1 << 20; +const CUSTOM_TOOL_NAME_PREFIX = 'custom.'; +const CUSTOM_TOOL_NAMESPACE_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/; + +type JsonSchema = Record; +type Validator = ReturnType; +export type ToolKind = 'page' | 'cdp'; + +export interface CustomToolMatch { + url_patterns: string[]; +} + +export interface CustomToolMetadata { + name: string; + title?: string; + description: string; + inputSchema: JsonSchema; + outputSchema?: JsonSchema; + annotations?: Record; +} + +export interface CustomToolFrameMatch { + frame_id: string; + session_id: string; + target_id: string | null; + top_target_id: string; + url: string; +} + +export interface CustomToolExecutionContext { + signal: AbortSignal; + matches: CustomToolFrameMatch[]; +} + +export interface CustomToolDefinitionInput { + kind: ToolKind; + match: CustomToolMatch; + tool: CustomToolMetadata; + execute: ( + input: Record, + context: CustomToolExecutionContext, + ) => unknown | Promise; +} + +export interface CustomToolDefinition extends CustomToolDefinitionInput { + id: string; + namespace: string; + registeredName: string; + revision: number; + inputValidator: Validator; + outputValidator?: Validator; + pageExecuteSource?: string; +} + +export interface CustomToolSummary { + id: string; + namespace: string; + kind: ToolKind; + match: CustomToolMatch; + tool: CustomToolMetadata; +} + +export interface AddCustomToolsInput { + namespace: string; + tools: CustomToolDefinitionInput[]; + forceOverwriteNamespace?: boolean; +} + +export function clone(value: T): T { + return structuredClone(value); +} + +export function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function registeredToolName(id: string, name: string): string { + const registeredName = `${CUSTOM_TOOL_NAME_PREFIX}${id}.${name}`; + if (registeredName.length > 128) { + throw new Error(`tool.name is too long after custom namespacing: ${name}`); + } + return registeredName; +} + +class CustomToolConflictError extends Error { + readonly code = 'custom_tool_conflict'; + + constructor(message: string) { + super(message); + this.name = 'CustomToolConflictError'; + } +} + +function formatToolSchemaError(error: { issues: Array<{ path: PropertyKey[]; message: string }> }): string { + return error.issues + .map((issue) => `${issue.path.length ? issue.path.join('.') : 'tool'}: ${issue.message}`) + .join('; '); +} + +function compileSchema(schema: JsonSchema, field: string): Validator { + try { + return new AjvJsonSchemaValidator().getValidator(schema); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`${field} is not a compilable JSON Schema: ${message}`); + } +} + +interface ParsedURLPattern { + scheme: 'http' | 'https' | '*'; + host: string; + path: RegExp; +} + +function wildcardRegexp(pattern: string): RegExp { + const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replaceAll('*', '.*'); + return new RegExp(`^${escaped}$`); +} + +function parseURLPattern(pattern: unknown): ParsedURLPattern { + if (typeof pattern !== 'string') { + throw new Error('match.url_patterns entries must be strings'); + } + const separator = pattern.indexOf('://'); + if (separator === -1) { + throw new Error('match.url_patterns entries must contain ://'); + } + const scheme = pattern.slice(0, separator); + if (scheme !== 'http' && scheme !== 'https' && scheme !== '*') { + throw new Error(`unsupported URL pattern scheme: ${scheme}`); + } + const remainder = pattern.slice(separator + 3); + const pathStart = remainder.indexOf('/'); + if (pathStart === -1) { + throw new Error('match.url_patterns entries must include a path'); + } + const host = remainder.slice(0, pathStart).toLowerCase(); + if (!host || (host.includes('*') && host !== '*' && !host.startsWith('*.')) || host.slice(2).includes('*')) { + throw new Error(`unsupported URL pattern host: ${host}`); + } + return {scheme, host, path: wildcardRegexp(remainder.slice(pathStart))}; +} + +export function normalizedURL(url: string): string { + const hash = url.indexOf('#'); + return hash === -1 ? url : url.slice(0, hash); +} + +export function matchesURLPattern(url: string, pattern: string): boolean { + const parsed = parseURLPattern(pattern); + let candidate: URL; + try { + candidate = new URL(normalizedURL(url)); + } catch { + return false; + } + const scheme = candidate.protocol.slice(0, -1); + if (parsed.scheme === '*' ? scheme !== 'http' && scheme !== 'https' : scheme !== parsed.scheme) return false; + + const host = candidate.host.toLowerCase(); + let hostMatches: boolean; + if (parsed.host === '*') { + hostMatches = true; + } else if (parsed.host.startsWith('*.')) { + const suffix = parsed.host.slice(2); + hostMatches = host === suffix || host.endsWith(`.${suffix}`); + } else { + hostMatches = host === parsed.host; + } + return hostMatches && parsed.path.test(`${candidate.pathname}${candidate.search}`); +} + +function definitionPublic(definition: CustomToolDefinition): CustomToolSummary { + return { + id: definition.id, + namespace: definition.namespace, + kind: definition.kind, + match: clone(definition.match), + tool: clone(definition.tool), + }; +} + +export class CustomToolDefinitions { + private definitions = new Map(); + private revision = 0; + private readonly publish: (tools: CustomToolSummary[]) => void; + + constructor(publish: (tools: CustomToolSummary[]) => void) { + this.publish = publish; + } + + get size(): number { return this.definitions.size; } + get(id: string): CustomToolDefinition | undefined { return this.definitions.get(id); } + values(): MapIterator { return this.definitions.values(); } + isCDP(id: string): boolean { return this.definitions.get(id)?.kind === 'cdp'; } + hasCDP(): boolean { + for (const definition of this.definitions.values()) { + if (definition.kind === 'cdp') return true; + } + return false; + } + + list(): CustomToolSummary[] { + return [...this.definitions.values()] + .map(definitionPublic) + .sort((a, b) => a.id.localeCompare(b.id)); + } + + async add(input: AddCustomToolsInput): Promise { + if (!input || typeof input !== 'object') throw new Error('custom tool batch must be an object'); + if (typeof input.namespace !== 'string' || !CUSTOM_TOOL_NAMESPACE_PATTERN.test(input.namespace)) { + throw new Error('namespace must be 1-128 ASCII letters, numbers, dots, underscores, or hyphens'); + } + if (!Array.isArray(input.tools) || input.tools.length === 0) { + throw new Error('tools must be a non-empty array'); + } + if (input.forceOverwriteNamespace !== undefined && typeof input.forceOverwriteNamespace !== 'boolean') { + throw new Error('forceOverwriteNamespace must be a boolean'); + } + + const {createId} = await import('@paralleldrive/cuid2'); + const nextDefinitions = new Map(this.definitions); + if (input.forceOverwriteNamespace) { + for (const definition of nextDefinitions.values()) { + if (definition.namespace === input.namespace) nextDefinitions.delete(definition.id); + } + } + const occupied = new Set( + [...nextDefinitions.values()].map((definition) => `${definition.namespace}\u0000${definition.tool.name}`), + ); + const additions: CustomToolDefinition[] = []; + for (const tool of input.tools) { + const name = isRecord(tool) && isRecord(tool.tool) && typeof tool.tool.name === 'string' + ? tool.tool.name + : ''; + const key = `${input.namespace}\u0000${name}`; + if (occupied.has(key)) { + throw new CustomToolConflictError(`custom tool already exists: ${input.namespace}/${name}`); + } + occupied.add(key); + let id: string; + do id = `ct_${createId()}`; while (this.definitions.has(id) || additions.some((item) => item.id === id)); + additions.push(this.validateDefinition(tool, id, input.namespace, this.revision + 1)); + } + + for (const definition of additions) nextDefinitions.set(definition.id, definition); + const previousDefinitions = this.definitions; + this.definitions = nextDefinitions; + try { + this.publish(this.list()); + } catch (error) { + this.definitions = previousDefinitions; + throw error; + } + this.revision++; + return additions.map(definitionPublic); + } + + remove(id: string): boolean { + const definition = this.definitions.get(id); + if (!definition) return false; + this.definitions.delete(id); + try { + this.publish(this.list()); + } catch (error) { + this.definitions.set(id, definition); + throw error; + } + this.revision++; + return true; + } + + private validateDefinition(input: CustomToolDefinitionInput, id: string, namespace: string, revision: number): CustomToolDefinition { + if (!input || typeof input !== 'object') throw new Error('definition must be an object'); + if (input.kind !== 'page' && input.kind !== 'cdp') { + throw new Error('definition.kind must be page or cdp'); + } + if (!input.match || !Array.isArray(input.match.url_patterns) || input.match.url_patterns.length === 0) { + throw new Error('definition.match.url_patterns must be a non-empty array'); + } + for (const pattern of input.match.url_patterns) parseURLPattern(pattern); + if (typeof input.execute !== 'function') throw new Error('definition.execute must be a function'); + + const toolResult = ToolSchema.safeParse(input.tool); + if (!toolResult.success) { + throw new Error(`invalid MCP tool definition: ${formatToolSchemaError(toolResult.error)}`); + } + if (input.tool.outputSchema !== undefined && !isRecord(input.tool.outputSchema)) { + throw new Error('tool.outputSchema must be an object'); + } + + const inputValidator = compileSchema(input.tool.inputSchema, 'tool.inputSchema'); + const outputValidator = input.tool.outputSchema === undefined + ? undefined + : compileSchema(input.tool.outputSchema, 'tool.outputSchema'); + const pageExecuteSource = input.kind === 'page' ? input.execute.toString() : undefined; + if (pageExecuteSource) { + try { + new Function(`return (${pageExecuteSource})`); + } catch (error) { + throw new Error(`definition.execute cannot be installed in the page: ${String(error)}`); + } + } + + return { + id, + namespace, + registeredName: registeredToolName(id, input.tool.name), + kind: input.kind, + match: clone(input.match), + tool: clone(input.tool), + execute: input.execute, + revision, + inputValidator, + outputValidator, + pageExecuteSource, + }; + } +} diff --git a/server/runtime/custom-webmcp-page-runtime.ts b/server/runtime/custom-webmcp-page-runtime.ts new file mode 100644 index 00000000..5859a65e --- /dev/null +++ b/server/runtime/custom-webmcp-page-runtime.ts @@ -0,0 +1,267 @@ +import type { BrowserReplCdpClient, CdpEvent } from './browser-cdp-client'; +import { clone, isRecord, MAX_OUTPUT_BYTES } from './custom-webmcp-definitions.ts'; +import type { CustomToolDefinition, CustomToolFrameMatch } from './custom-webmcp-definitions.ts'; + +export const PAGE_RUNTIME_KEY = '__kernelCustomWebMCP'; +export const MAIN_PAGE_RUNTIME_KEY = '__kernelCustomWebMCPPage'; +export const BINDING_NAME = '__kernelCustomWebMCPInvoke'; +const RECONCILE_COMMAND_TIMEOUT_MS = 5_000; +const MAX_ERROR_BYTES = 64 << 10; + +interface RuntimeEvaluateResult { + exceptionDetails?: { + exception?: {description?: string}; + text?: string; + }; + result?: {value?: unknown}; +} + +export function errorMessage(error: unknown): string { + const message = error instanceof Error ? error.message : String(error); + if (Buffer.byteLength(message) <= MAX_ERROR_BYTES) return message; + return `${Buffer.from(message).subarray(0, MAX_ERROR_BYTES).toString('utf8')}...[truncated]`; +} + +export class CustomWebMCPPageRuntime { + private activeInvocations = new Map(); + private readonly client: BrowserReplCdpClient; + private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; + private readonly resolve: (sessionId: string, id: string, revision: number) => + {definition: CustomToolDefinition; matches: CustomToolFrameMatch[]} | undefined; + + constructor( + client: BrowserReplCdpClient, + runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, + resolve: (sessionId: string, id: string, revision: number) => + {definition: CustomToolDefinition; matches: CustomToolFrameMatch[]} | undefined, + ) { + this.client = client; + this.runInvocation = runInvocation; + this.resolve = resolve; + } + + abortAll(): void { + for (const controller of this.activeInvocations.values()) controller.abort(); + this.activeInvocations.clear(); + } + + async invokeCDP( + definition: CustomToolDefinition, + matches: CustomToolFrameMatch[], + input: Record, + signal?: AbortSignal, + ): Promise { + const inputResult = definition.inputValidator(input); + if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); + const executionSignal = signal ?? new AbortController().signal; + const output = await this.runInvocation(executionSignal, async () => definition.execute( + inputResult.data as Record, + {signal: executionSignal, matches: clone(matches)}, + )); + if (definition.outputValidator) { + const outputResult = definition.outputValidator(output); + if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); + } + const result = output ?? null; + if (Buffer.byteLength(JSON.stringify(result)) > MAX_OUTPUT_BYTES) { + throw new Error('custom WebMCP output exceeds 1 MiB'); + } + return result; + } + + async dispose(sessionId: string, runtimeKey: string, contextId?: number): Promise { + const params: Record = { + expression: `globalThis[${JSON.stringify(runtimeKey)}]?.dispose?.()`, + awaitPromise: true, + }; + if (contextId !== undefined) params.contextId = contextId; + try { + await this.client.send('Runtime.evaluate', params, sessionId, 1_000); + return true; + } catch (error) { + const message = errorMessage(error); + return message.includes('Cannot find context') || + message.includes('No target with given id') || + message.includes('Session with given id not found'); + } + } + + async install( + sessionId: string, + definitions: CustomToolDefinition[], + runtimeKey: string, + contextId?: number, + ): Promise { + const definitionsSource = definitions.map((definition) => { + const metadata = JSON.stringify({ + id: definition.id, + kind: definition.kind, + tool: {...definition.tool, name: definition.registeredName}, + revision: definition.revision, + }); + if (definition.kind === 'cdp') return metadata; + return `${metadata.slice(0, -1)},"execute":(${definition.pageExecuteSource})}`; + }).join(','); + const expression = ` + (async () => { + const key = ${JSON.stringify(runtimeKey)}; + const definitions = [${definitionsSource}]; + let runtime = globalThis[key]; + if (!runtime) { + const controllers = new Map(); + const pending = new Map(); + const revisions = new Map(); + runtime = { + controllers, + pending, + revisions, + resolveInvocation(id, value) { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.resolve(value); + }, + rejectInvocation(id, message) { + const invocation = pending.get(id); + if (!invocation) return; + pending.delete(id); + invocation.reject(new Error(message)); + }, + dispose() { + for (const controller of controllers.values()) controller.abort(); + controllers.clear(); + revisions.clear(); + }, + }; + globalThis[key] = runtime; + } + const {controllers, pending, revisions} = runtime; + const desiredRevisions = new Map(definitions.map(definition => [definition.id, definition.revision])); + for (const [id, controller] of controllers) { + if (desiredRevisions.get(id) !== revisions.get(id)) { + controller.abort(); + controllers.delete(id); + revisions.delete(id); + } + } + const errors = []; + for (const definition of definitions) { + if (controllers.has(definition.id)) continue; + const controller = new AbortController(); + controllers.set(definition.id, controller); + revisions.set(definition.id, definition.revision); + let execute; + if (definition.kind === 'page') { + execute = definition.execute; + } else { + execute = (input, {signal} = {}) => { + const invocationId = crypto.randomUUID(); + return new Promise((resolve, reject) => { + pending.set(invocationId, {resolve, reject}); + signal?.addEventListener('abort', () => { + if (!pending.has(invocationId)) return; + pending.delete(invocationId); + globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ + type: 'cancel', + invocation_id: invocationId, + })); + reject(new DOMException('Canceled', 'AbortError')); + }, {once: true}); + globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ + type: 'invoke', + invocation_id: invocationId, + definition_id: definition.id, + revision: definition.revision, + input, + })); + }); + }; + } + try { + await document.modelContext.registerTool({...definition.tool, execute}, {signal: controller.signal}); + } catch (error) { + controller.abort(); + controllers.delete(definition.id); + revisions.delete(definition.id); + errors.push(\`\${definition.id}: \${error instanceof Error ? error.message : String(error)}\`); + } + } + return errors; + })() + `; + const params: Record = { expression, awaitPromise: true, returnByValue: true }; + if (contextId !== undefined) params.contextId = contextId; + const result = await this.client.send( + 'Runtime.evaluate', + params, + sessionId, + RECONCILE_COMMAND_TIMEOUT_MS, + ); + if (result.exceptionDetails) { + throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? 'tool installation failed'); + } + if (!Array.isArray(result.result?.value)) throw new Error('tool installation returned no result'); + return result.result.value; + } + + async handleBinding(event: CdpEvent): Promise { + const params = event.params as { name?: string; payload?: string; executionContextId?: number }; + if (params.name !== BINDING_NAME || !event.sessionId || params.executionContextId === undefined) return; + let payload: unknown; + try { + payload = JSON.parse(params.payload ?? ''); + } catch { + return; + } + if (!isRecord(payload)) return; + const message = payload; + if (message.type === 'cancel' && typeof message.invocation_id === 'string') { + this.activeInvocations.get(message.invocation_id)?.abort(); + return; + } + if ( + message.type !== 'invoke' || + typeof message.invocation_id !== 'string' || + typeof message.definition_id !== 'string' || + typeof message.revision !== 'number' || + !isRecord(message.input) + ) { + return; + } + + const invocation = this.resolve(event.sessionId, message.definition_id, message.revision); + if (!invocation) { + await this.respond(event.sessionId, params.executionContextId, message.invocation_id, undefined, 'custom tool is no longer registered'); + return; + } + + const controller = new AbortController(); + this.activeInvocations.set(message.invocation_id, controller); + try { + const output = await this.invokeCDP(invocation.definition, invocation.matches, message.input, controller.signal); + await this.respond(event.sessionId, params.executionContextId, message.invocation_id, output); + } catch (error) { + await this.respond(event.sessionId, params.executionContextId, message.invocation_id, undefined, errorMessage(error)); + } finally { + this.activeInvocations.delete(message.invocation_id); + } + } + + private async respond(sessionId: string, contextId: number, invocationId: string, output?: unknown, error?: string): Promise { + let expression: string; + if (error !== undefined) { + expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.rejectInvocation(${JSON.stringify(invocationId)}, ${JSON.stringify(error)})`; + } else { + const serialized = JSON.stringify(output ?? null); + if (Buffer.byteLength(serialized) > MAX_OUTPUT_BYTES) { + throw new Error('custom WebMCP output exceeds 1 MiB'); + } + expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.resolveInvocation(${JSON.stringify(invocationId)}, ${serialized})`; + } + try { + await this.client.send('Runtime.evaluate', { expression, contextId, awaitPromise: true }, sessionId); + } catch { + // The registration document may have navigated after starting the tool. + } + } +} diff --git a/server/runtime/custom-webmcp.ts b/server/runtime/custom-webmcp.ts index d461f758..dfb8c104 100644 --- a/server/runtime/custom-webmcp.ts +++ b/server/runtime/custom-webmcp.ts @@ -1,69 +1,14 @@ -import { ToolSchema } from '@modelcontextprotocol/core'; -import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv'; import type { BrowserReplCdpClient, CdpEvent, CdpTarget } from './browser-cdp-client'; +import { CustomToolDefinitions, matchesURLPattern, normalizedURL } from './custom-webmcp-definitions.ts'; +import type { AddCustomToolsInput, CustomToolDefinition, CustomToolFrameMatch, CustomToolSummary } from './custom-webmcp-definitions.ts'; +import { BINDING_NAME, CustomWebMCPPageRuntime, errorMessage, MAIN_PAGE_RUNTIME_KEY, PAGE_RUNTIME_KEY } from './custom-webmcp-page-runtime.ts'; + +export { matchesURLPattern } from './custom-webmcp-definitions.ts'; const WORLD_NAME = 'kernel-custom-webmcp'; -const BINDING_NAME = '__kernelCustomWebMCPInvoke'; -const PAGE_RUNTIME_KEY = '__kernelCustomWebMCP'; -const MAIN_PAGE_RUNTIME_KEY = '__kernelCustomWebMCPPage'; const RECONNECT_DELAY_MS = 250; const RECONCILE_EVENT_DELAY_MS = 50; const RECONCILE_COMMAND_TIMEOUT_MS = 5_000; -const MAX_OUTPUT_BYTES = 1 << 20; -const MAX_ERROR_BYTES = 64 << 10; -const CUSTOM_TOOL_NAME_PREFIX = 'custom.'; -const CUSTOM_TOOL_NAMESPACE_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/; - -type JsonSchema = Record; -type Validator = ReturnType; - -type ToolKind = 'page' | 'cdp'; - -interface CustomToolMatch { - url_patterns: string[]; -} - -interface CustomToolMetadata { - name: string; - title?: string; - description: string; - inputSchema: JsonSchema; - outputSchema?: JsonSchema; - annotations?: Record; -} - -interface CustomToolDefinitionInput { - kind: ToolKind; - match: CustomToolMatch; - tool: CustomToolMetadata; - execute: ( - input: Record, - context: CustomToolExecutionContext, - ) => unknown | Promise; -} - -interface CustomToolDefinition extends CustomToolDefinitionInput { - id: string; - namespace: string; - registeredName: string; - revision: number; - inputValidator: Validator; - outputValidator?: Validator; - pageExecuteSource?: string; -} - -export interface CustomToolFrameMatch { - frame_id: string; - session_id: string; - target_id: string | null; - top_target_id: string; - url: string; -} - -export interface CustomToolExecutionContext { - signal: AbortSignal; - matches: CustomToolFrameMatch[]; -} interface CdpFrameTree { frame: { @@ -74,14 +19,6 @@ interface CdpFrameTree { childFrames?: CdpFrameTree[]; } -interface RuntimeEvaluateResult { - exceptionDetails?: { - exception?: {description?: string}; - text?: string; - }; - result?: {value?: unknown}; -} - interface FrameInfo { id: string; sessionId: string; @@ -106,167 +43,24 @@ interface PageState { errors: string[]; } -interface ActiveInvocation { - controller: AbortController; -} - -export interface CustomToolSummary { - id: string; - namespace: string; - kind: ToolKind; - match: CustomToolMatch; - tool: CustomToolMetadata; -} - -export interface AddCustomToolsInput { - namespace: string; - tools: CustomToolDefinitionInput[]; - forceOverwriteNamespace?: boolean; -} - -function clone(value: T): T { - return structuredClone(value); -} - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function newCustomToolID(createId: () => string): string { - return `ct_${createId()}`; -} - -function registeredToolName(id: string, name: string): string { - const registeredName = `${CUSTOM_TOOL_NAME_PREFIX}${id}.${name}`; - if (registeredName.length > 128) { - throw new Error(`tool.name is too long after custom namespacing: ${name}`); - } - return registeredName; -} - -class CustomToolConflictError extends Error { - readonly code = 'custom_tool_conflict'; - - constructor(message: string) { - super(message); - this.name = 'CustomToolConflictError'; - } -} - class CustomToolNotFoundError extends Error { readonly code = 'custom_tool_not_found'; } -function errorMessage(error: unknown): string { - const message = error instanceof Error ? error.message : String(error); - if (Buffer.byteLength(message) <= MAX_ERROR_BYTES) return message; - return `${Buffer.from(message).subarray(0, MAX_ERROR_BYTES).toString('utf8')}...[truncated]`; -} - -function formatToolSchemaError(error: { issues: Array<{ path: PropertyKey[]; message: string }> }): string { - return error.issues - .map((issue) => `${issue.path.length ? issue.path.join('.') : 'tool'}: ${issue.message}`) - .join('; '); -} - -function compileSchema(schema: JsonSchema, field: string): Validator { - try { - return new AjvJsonSchemaValidator().getValidator(schema); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - throw new Error(`${field} is not a compilable JSON Schema: ${message}`); - } -} - -interface ParsedURLPattern { - scheme: 'http' | 'https' | '*'; - host: string; - path: RegExp; -} - -function wildcardRegexp(pattern: string): RegExp { - const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replaceAll('*', '.*'); - return new RegExp(`^${escaped}$`); -} - -function parseURLPattern(pattern: unknown): ParsedURLPattern { - if (typeof pattern !== 'string') { - throw new Error('match.url_patterns entries must be strings'); - } - const separator = pattern.indexOf('://'); - if (separator === -1) { - throw new Error('match.url_patterns entries must contain ://'); - } - const scheme = pattern.slice(0, separator); - if (scheme !== 'http' && scheme !== 'https' && scheme !== '*') { - throw new Error(`unsupported URL pattern scheme: ${scheme}`); - } - const remainder = pattern.slice(separator + 3); - const pathStart = remainder.indexOf('/'); - if (pathStart === -1) { - throw new Error('match.url_patterns entries must include a path'); - } - const host = remainder.slice(0, pathStart).toLowerCase(); - if (!host || (host.includes('*') && host !== '*' && !host.startsWith('*.')) || host.slice(2).includes('*')) { - throw new Error(`unsupported URL pattern host: ${host}`); - } - return {scheme, host, path: wildcardRegexp(remainder.slice(pathStart))}; -} - -function normalizedURL(url: string): string { - const hash = url.indexOf('#'); - return hash === -1 ? url : url.slice(0, hash); -} - -export function matchesURLPattern(url: string, pattern: string): boolean { - const parsed = parseURLPattern(pattern); - let candidate: URL; - try { - candidate = new URL(normalizedURL(url)); - } catch { - return false; - } - const scheme = candidate.protocol.slice(0, -1); - if (parsed.scheme === '*' ? scheme !== 'http' && scheme !== 'https' : scheme !== parsed.scheme) return false; - - const host = candidate.host.toLowerCase(); - let hostMatches: boolean; - if (parsed.host === '*') { - hostMatches = true; - } else if (parsed.host.startsWith('*.')) { - const suffix = parsed.host.slice(2); - hostMatches = host === suffix || host.endsWith(`.${suffix}`); - } else { - hostMatches = host === parsed.host; - } - return hostMatches && parsed.path.test(`${candidate.pathname}${candidate.search}`); -} - function flattenFrames(frame: FrameInfo): FrameInfo[] { const frames = [frame]; for (const child of frame.children ?? []) frames.push(...flattenFrames(child)); return frames; } -function definitionPublic(definition: CustomToolDefinition): CustomToolSummary { - return { - id: definition.id, - namespace: definition.namespace, - kind: definition.kind, - match: clone(definition.match), - tool: clone(definition.tool), - }; -} - export class CustomWebMCPRegistry { - private definitions = new Map(); - private revision = 0; + private readonly definitions: CustomToolDefinitions; + private readonly pageRuntime: CustomWebMCPPageRuntime; private emptyRegistryCleaned = false; private pages = new Map(); private sessions = new Map(); private iframeSessions = new Map(); private iframeTargetsBySession = new Map(); - private activeInvocations = new Map(); private reconciliation: Promise | null = null; private reconcileAgain = false; private reconcileTimer: ReturnType | null = null; @@ -277,17 +71,21 @@ export class CustomWebMCPRegistry { private readonly unsubscribeDisconnect: () => void; private onError?: (message: string) => void; private readonly client: BrowserReplCdpClient; - private readonly runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise; - private readonly publishDefinitions: (tools: CustomToolSummary[], revision: number) => void; constructor( client: BrowserReplCdpClient, runInvocation: (signal: AbortSignal, callback: () => Promise) => Promise, - publishDefinitions: (tools: CustomToolSummary[], revision: number) => void, + publishDefinitions: (tools: CustomToolSummary[]) => void, ) { this.client = client; - this.runInvocation = runInvocation; - this.publishDefinitions = publishDefinitions; + this.definitions = new CustomToolDefinitions(publishDefinitions); + this.pageRuntime = new CustomWebMCPPageRuntime(client, runInvocation, (sessionId, id, revision) => { + const targetId = this.sessions.get(sessionId); + const page = targetId ? this.pages.get(targetId) : undefined; + const definition = this.definitions.get(id); + if (!page || !definition || definition.revision !== revision) return undefined; + return {definition, matches: page.matches.get(id) ?? []}; + }); this.unsubscribeEvent = client.subscribeEvents((event) => this.handleEvent(event)); this.unsubscribeDisconnect = client.subscribeDisconnect(() => this.handleDisconnect()); this.scheduleReconcile(); @@ -298,81 +96,23 @@ export class CustomWebMCPRegistry { } add = async (input: AddCustomToolsInput): Promise => { - if (!input || typeof input !== 'object') throw new Error('custom tool batch must be an object'); - if (typeof input.namespace !== 'string' || !CUSTOM_TOOL_NAMESPACE_PATTERN.test(input.namespace)) { - throw new Error('namespace must be 1-128 ASCII letters, numbers, dots, underscores, or hyphens'); - } - if (!Array.isArray(input.tools) || input.tools.length === 0) { - throw new Error('tools must be a non-empty array'); - } - if (input.forceOverwriteNamespace !== undefined && typeof input.forceOverwriteNamespace !== 'boolean') { - throw new Error('forceOverwriteNamespace must be a boolean'); - } - - const {createId} = await import('@paralleldrive/cuid2'); - const nextDefinitions = new Map(this.definitions); - if (input.forceOverwriteNamespace) { - for (const definition of nextDefinitions.values()) { - if (definition.namespace === input.namespace) nextDefinitions.delete(definition.id); - } - } - const occupied = new Set( - [...nextDefinitions.values()].map((definition) => `${definition.namespace}\u0000${definition.tool.name}`), - ); - const additions: CustomToolDefinition[] = []; - for (const tool of input.tools) { - const name = isRecord(tool) && isRecord(tool.tool) && typeof tool.tool.name === 'string' - ? tool.tool.name - : ''; - const key = `${input.namespace}\u0000${name}`; - if (occupied.has(key)) { - throw new CustomToolConflictError(`custom tool already exists: ${input.namespace}/${name}`); - } - occupied.add(key); - let id: string; - do id = newCustomToolID(createId); while (this.definitions.has(id) || additions.some((item) => item.id === id)); - additions.push(this.validateDefinition(tool, id, input.namespace, this.revision + 1)); - } - - for (const definition of additions) nextDefinitions.set(definition.id, definition); - const previousDefinitions = this.definitions; - this.definitions = nextDefinitions; - try { - this.publishDefinitions(this.list(), this.revision + 1); - } catch (error) { - this.definitions = previousDefinitions; - throw error; - } - this.revision++; + const added = await this.definitions.add(input); this.scheduleReconcile(); await this.settleReconciliation(); - return additions.map(definitionPublic); + return added; }; remove = async (id: string): Promise => { - const definition = this.definitions.get(id); - if (!definition) return false; - this.definitions.delete(id); - try { - this.publishDefinitions(this.list(), this.revision + 1); - } catch (error) { - this.definitions.set(id, definition); - throw error; - } - this.revision++; + if (!this.definitions.remove(id)) return false; if (this.definitions.size === 0) this.emptyRegistryCleaned = false; this.scheduleReconcile(); await this.settleReconciliation(); return true; }; - list = (): CustomToolSummary[] => { - return [...this.definitions.values()] - .map(definitionPublic) - .sort((a, b) => a.id.localeCompare(b.id)); - }; - - isCDP = (id: string): boolean => this.definitions.get(id)?.kind === 'cdp'; + list = (): CustomToolSummary[] => this.definitions.list(); + isCDP = (id: string): boolean => this.definitions.isCDP(id); + hasCDP = (): boolean => this.definitions.hasCDP(); invokeCDP = async (id: string, targetId: string, input: Record, signal?: AbortSignal): Promise => { await this.reconcile(); @@ -381,22 +121,7 @@ export class CustomWebMCPRegistry { if (definition?.kind !== 'cdp' || !matches?.length) { throw new CustomToolNotFoundError('custom tool is no longer available; discover tools again'); } - const inputResult = definition.inputValidator(input); - if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); - const executionSignal = signal ?? new AbortController().signal; - const output = await this.runInvocation(executionSignal, async () => definition.execute( - inputResult.data as Record, - {signal: executionSignal, matches: clone(matches)}, - )); - if (definition.outputValidator) { - const outputResult = definition.outputValidator(output); - if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); - } - const result = output ?? null; - if (Buffer.byteLength(JSON.stringify(result)) > MAX_OUTPUT_BYTES) { - throw new Error('custom WebMCP output exceeds 1 MiB'); - } - return result; + return this.pageRuntime.invokeCDP(definition, matches, input, signal); }; private async settleReconciliation(): Promise { @@ -420,11 +145,10 @@ export class CustomWebMCPRegistry { new Promise((resolve) => setTimeout(resolve, 500)), ]); } - for (const invocation of this.activeInvocations.values()) invocation.controller.abort(); - this.activeInvocations.clear(); + this.pageRuntime.abortAll(); await Promise.all([...this.pages.values()].map(async (page) => { - await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); - if (page.contextId !== null) await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + await this.pageRuntime.dispose(page.sessionId, MAIN_PAGE_RUNTIME_KEY); + if (page.contextId !== null) await this.pageRuntime.dispose(page.sessionId, PAGE_RUNTIME_KEY, page.contextId); await this.detachPage(page); })); await Promise.all([...this.iframeSessions.values()].map((frame) => this.detachSession(frame.sessionId))); @@ -434,61 +158,9 @@ export class CustomWebMCPRegistry { this.iframeTargetsBySession.clear(); } - private validateDefinition( - input: CustomToolDefinitionInput, - id: string, - namespace: string, - revision: number, - ): CustomToolDefinition { - if (!input || typeof input !== 'object') throw new Error('definition must be an object'); - if (input.kind !== 'page' && input.kind !== 'cdp') { - throw new Error('definition.kind must be page or cdp'); - } - if (!input.match || !Array.isArray(input.match.url_patterns) || input.match.url_patterns.length === 0) { - throw new Error('definition.match.url_patterns must be a non-empty array'); - } - for (const pattern of input.match.url_patterns) parseURLPattern(pattern); - if (typeof input.execute !== 'function') throw new Error('definition.execute must be a function'); - - const toolResult = ToolSchema.safeParse(input.tool); - if (!toolResult.success) { - throw new Error(`invalid MCP tool definition: ${formatToolSchemaError(toolResult.error)}`); - } - if (input.tool.outputSchema !== undefined && !isRecord(input.tool.outputSchema)) { - throw new Error('tool.outputSchema must be an object'); - } - - const inputValidator = compileSchema(input.tool.inputSchema, 'tool.inputSchema'); - const outputValidator = input.tool.outputSchema === undefined - ? undefined - : compileSchema(input.tool.outputSchema, 'tool.outputSchema'); - const pageExecuteSource = input.kind === 'page' ? input.execute.toString() : undefined; - if (pageExecuteSource) { - try { - new Function(`return (${pageExecuteSource})`); - } catch (error) { - throw new Error(`definition.execute cannot be installed in the page: ${String(error)}`); - } - } - - return { - id, - namespace, - registeredName: registeredToolName(id, input.tool.name), - kind: input.kind, - match: clone(input.match), - tool: clone(input.tool), - execute: input.execute, - revision, - inputValidator, - outputValidator, - pageExecuteSource, - }; - } - private handleEvent(event: CdpEvent): void { if (event.method === 'Runtime.bindingCalled') { - void this.handleBinding(event); + void this.pageRuntime.handleBinding(event); return; } if (event.method === 'Target.detachedFromTarget') { @@ -613,24 +285,26 @@ export class CustomWebMCPRegistry { const page = this.pages.get(target.targetId); if (page) { const message = errorMessage(error); + if (page.errors[0] !== message) this.reportError(error); page.errors = [message]; + page.signature = ''; if (message.includes('Cannot find context') || message.includes('Session with given id not found')) { page.contextId = null; page.contextDocumentKey = ''; - page.signature = ''; } } else { this.reportError(error); } + this.retryReconcile(); } })); if (this.definitions.size === 0) { const cleanupGeneration = this.connectionGeneration; const pageCleanup = await Promise.all([...this.pages.values()].map(async (page) => { - const mainDisposed = await this.disposePageRuntime(page, MAIN_PAGE_RUNTIME_KEY); + const mainDisposed = await this.pageRuntime.dispose(page.sessionId, MAIN_PAGE_RUNTIME_KEY); const isolatedDisposed = page.contextId === null || - await this.disposePageRuntime(page, PAGE_RUNTIME_KEY, page.contextId); + await this.pageRuntime.dispose(page.sessionId, PAGE_RUNTIME_KEY, page.contextId); const detached = await this.detachPage(page); return mainDisposed && isolatedDisposed && detached; })); @@ -758,18 +432,26 @@ export class CustomWebMCPRegistry { if (isolatedContextId === null) throw new Error('custom WebMCP isolated world is unavailable'); const definitions = [...page.matches.keys()].map((id) => this.definitions.get(id)!); - const pageErrors = await this.installPageDefinitions( - page, + const pageErrors = await this.pageRuntime.install( + page.sessionId, definitions.filter((definition) => definition.kind === 'page'), MAIN_PAGE_RUNTIME_KEY, ); - const cdpErrors = await this.installPageDefinitions( - page, + const cdpErrors = await this.pageRuntime.install( + page.sessionId, definitions.filter((definition) => definition.kind === 'cdp'), PAGE_RUNTIME_KEY, isolatedContextId, ); - page.errors = [...pageErrors, ...cdpErrors]; + const errors = [...pageErrors, ...cdpErrors]; + const changed = errors.join('; ') !== page.errors.join('; '); + page.errors = errors; + if (errors.length) { + page.signature = ''; + if (changed) this.reportError(new Error(`custom WebMCP registration failed: ${errors.join('; ')}`)); + this.retryReconcile(); + return; + } page.documentKey = documentKey; page.signature = signature; } @@ -800,228 +482,6 @@ export class CustomWebMCPRegistry { return session; } - private async disposePageRuntime(page: PageState, runtimeKey: string, contextId?: number): Promise { - const params: Record = { - expression: `globalThis[${JSON.stringify(runtimeKey)}]?.dispose?.()`, - awaitPromise: true, - }; - if (contextId !== undefined) params.contextId = contextId; - try { - await this.client.send('Runtime.evaluate', params, page.sessionId, 1_000); - return true; - } catch (error) { - const message = errorMessage(error); - return message.includes('Cannot find context') || - message.includes('No target with given id') || - message.includes('Session with given id not found'); - } - } - - private async installPageDefinitions( - page: PageState, - definitions: CustomToolDefinition[], - runtimeKey: string, - contextId?: number, - ): Promise { - const definitionsSource = definitions.map((definition) => { - const metadata = JSON.stringify({ - id: definition.id, - kind: definition.kind, - tool: {...definition.tool, name: definition.registeredName}, - revision: definition.revision, - }); - if (definition.kind === 'cdp') return metadata; - return `${metadata.slice(0, -1)},"execute":(${definition.pageExecuteSource})}`; - }).join(','); - const expression = ` - (async () => { - const key = ${JSON.stringify(runtimeKey)}; - const definitions = [${definitionsSource}]; - let runtime = globalThis[key]; - if (!runtime) { - const controllers = new Map(); - const pending = new Map(); - const revisions = new Map(); - runtime = { - controllers, - pending, - revisions, - resolveInvocation(id, value) { - const invocation = pending.get(id); - if (!invocation) return; - pending.delete(id); - invocation.resolve(value); - }, - rejectInvocation(id, message) { - const invocation = pending.get(id); - if (!invocation) return; - pending.delete(id); - invocation.reject(new Error(message)); - }, - dispose() { - for (const controller of controllers.values()) controller.abort(); - controllers.clear(); - revisions.clear(); - }, - }; - globalThis[key] = runtime; - } - const {controllers, pending, revisions} = runtime; - const desiredRevisions = new Map(definitions.map(definition => [definition.id, definition.revision])); - for (const [id, controller] of controllers) { - if (desiredRevisions.get(id) !== revisions.get(id)) { - controller.abort(); - controllers.delete(id); - revisions.delete(id); - } - } - const errors = []; - for (const definition of definitions) { - if (controllers.has(definition.id)) continue; - const controller = new AbortController(); - controllers.set(definition.id, controller); - revisions.set(definition.id, definition.revision); - let execute; - if (definition.kind === 'page') { - execute = definition.execute; - } else { - execute = (input, {signal} = {}) => { - const invocationId = crypto.randomUUID(); - return new Promise((resolve, reject) => { - pending.set(invocationId, {resolve, reject}); - signal?.addEventListener('abort', () => { - if (!pending.has(invocationId)) return; - pending.delete(invocationId); - globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ - type: 'cancel', - invocation_id: invocationId, - })); - reject(new DOMException('Canceled', 'AbortError')); - }, {once: true}); - globalThis[${JSON.stringify(BINDING_NAME)}](JSON.stringify({ - type: 'invoke', - invocation_id: invocationId, - definition_id: definition.id, - revision: definition.revision, - input, - })); - }); - }; - } - try { - await document.modelContext.registerTool({...definition.tool, execute}, {signal: controller.signal}); - } catch (error) { - controller.abort(); - controllers.delete(definition.id); - revisions.delete(definition.id); - errors.push(\`\${definition.id}: \${error instanceof Error ? error.message : String(error)}\`); - } - } - return errors; - })() - `; - const params: Record = { expression, awaitPromise: true, returnByValue: true }; - if (contextId !== undefined) params.contextId = contextId; - const result = await this.client.send( - 'Runtime.evaluate', - params, - page.sessionId, - RECONCILE_COMMAND_TIMEOUT_MS, - ); - if (result.exceptionDetails) { - throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? 'tool installation failed'); - } - return Array.isArray(result.result?.value) ? result.result.value : []; - } - - private async handleBinding(event: CdpEvent): Promise { - const params = event.params as { name?: string; payload?: string; executionContextId?: number }; - if (params.name !== BINDING_NAME || !event.sessionId || params.executionContextId === undefined) return; - let payload: unknown; - try { - payload = JSON.parse(params.payload ?? ''); - } catch { - return; - } - if (!isRecord(payload)) return; - if (payload.type === 'cancel' && typeof payload.invocation_id === 'string') { - this.activeInvocations.get(payload.invocation_id)?.controller.abort(); - return; - } - if ( - payload.type !== 'invoke' || - typeof payload.invocation_id !== 'string' || - typeof payload.definition_id !== 'string' || - typeof payload.revision !== 'number' || - !payload.input || - typeof payload.input !== 'object' || - Array.isArray(payload.input) - ) { - return; - } - - const targetId = this.sessions.get(event.sessionId); - const page = targetId ? this.pages.get(targetId) : undefined; - const definition = this.definitions.get(payload.definition_id); - if (!targetId || !page || !definition || definition.revision !== payload.revision) { - await this.respondToInvocation(event.sessionId, params.executionContextId, payload.invocation_id, undefined, 'custom tool is no longer registered'); - return; - } - - const controller = new AbortController(); - this.activeInvocations.set(payload.invocation_id, {controller}); - try { - const inputResult = definition.inputValidator(payload.input); - if (!inputResult.valid) throw new Error(`input failed JSON Schema validation: ${inputResult.errorMessage}`); - const output = await this.runInvocation(controller.signal, async () => definition.execute( - inputResult.data as Record, - { - signal: controller.signal, - matches: clone(page.matches.get(definition.id) ?? []), - }, - )); - if (definition.outputValidator) { - const outputResult = definition.outputValidator(output); - if (!outputResult.valid) throw new Error(`output failed JSON Schema validation: ${outputResult.errorMessage}`); - } - await this.respondToInvocation(event.sessionId, params.executionContextId, payload.invocation_id, output); - } catch (error) { - await this.respondToInvocation( - event.sessionId, - params.executionContextId, - payload.invocation_id, - undefined, - errorMessage(error), - ); - } finally { - this.activeInvocations.delete(payload.invocation_id); - } - } - - private async respondToInvocation( - sessionId: string, - contextId: number, - invocationId: string, - output?: unknown, - error?: string, - ): Promise { - let expression: string; - if (error !== undefined) { - expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.rejectInvocation(${JSON.stringify(invocationId)}, ${JSON.stringify(error)})`; - } else { - const serialized = JSON.stringify(output ?? null); - if (Buffer.byteLength(serialized) > MAX_OUTPUT_BYTES) { - throw new Error('custom WebMCP output exceeds 1 MiB'); - } - expression = `globalThis[${JSON.stringify(PAGE_RUNTIME_KEY)}]?.resolveInvocation(${JSON.stringify(invocationId)}, ${serialized})`; - } - try { - await this.client.send('Runtime.evaluate', { expression, contextId, awaitPromise: true }, sessionId); - } catch { - // The registration document may have navigated after starting the tool. - } - } - private async detachPage(page: PageState): Promise { return this.detachSession(page.sessionId); } diff --git a/server/runtime/tsconfig.json b/server/runtime/tsconfig.json index fdbb542a..6b4ddd49 100644 --- a/server/runtime/tsconfig.json +++ b/server/runtime/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", + "allowImportingTsExtensions": true, "strict": true, "skipLibCheck": true, "types": ["node"] From 26f56c9f02b160fe263e315c34d9c61be6c91708 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:52:09 +0000 Subject: [PATCH 16/18] Publish custom discovery generation after REPL readiness --- server/cmd/api/api/browser_repl.go | 2 +- server/cmd/api/api/custom_webmcp_test.go | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/server/cmd/api/api/browser_repl.go b/server/cmd/api/api/browser_repl.go index 830b3a88..c3222fcf 100644 --- a/server/cmd/api/api/browser_repl.go +++ b/server/cmd/api/api/browser_repl.go @@ -294,13 +294,13 @@ func (m *browserReplManager) startLocked(ctx context.Context) error { child.done <- cmd.Wait() }() m.child = child - m.setCustomToolsReplID(replID) deadline := time.Now().Add(browserReplStartupTimeout) for { conn, err := net.DialTimeout("unix", socketPath, 200*time.Millisecond) if err == nil { conn.Close() + m.setCustomToolsReplID(replID) log.Info("browser REPL ready", "repl_id", replID) return nil } diff --git a/server/cmd/api/api/custom_webmcp_test.go b/server/cmd/api/api/custom_webmcp_test.go index 2f9cc2c9..782cd562 100644 --- a/server/cmd/api/api/custom_webmcp_test.go +++ b/server/cmd/api/api/custom_webmcp_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "strings" "testing" "time" @@ -110,6 +111,39 @@ func TestCustomWebMCPToolsUseBrowserReplLifecycle(t *testing.T) { require.Empty(t, listed.(oapi.ListCustomWebMCPTools200JSONResponse).Tools) } +func TestCustomWebMCPSnapshotBeforeReplReady(t *testing.T) { + bundle := ensureBrowserReplBundle(t) + svc := newBrowserReplSvc(t) + marker := filepath.Join(t.TempDir(), "starting") + wrapper := filepath.Join(t.TempDir(), "delayed-repl.js") + code := fmt.Sprintf(`require('fs').writeFileSync(%q, '1'); setTimeout(() => require(%q), 700);`, marker, bundle) + require.NoError(t, os.WriteFile(wrapper, []byte(code), 0o600)) + t.Setenv("BROWSER_REPL_SCRIPT", wrapper) + + done := make(chan error, 1) + go func() { + response, err := svc.ExecuteBrowserRepl(context.Background(), oapi.ExecuteBrowserReplRequestObject{ + Body: &oapi.ExecuteBrowserReplJSONRequestBody{Code: "1"}, + }) + if err == nil { + result, ok := response.(oapi.ExecuteBrowserRepl200JSONResponse) + if !ok || !result.Success { + err = fmt.Errorf("unexpected response: %#v", response) + } + } + done <- err + }() + require.Eventually(t, func() bool { + _, err := os.Stat(marker) + return err == nil + }, 5*time.Second, 10*time.Millisecond) + tools, err := svc.browserRepl.customToolsSnapshot() + require.NoError(t, err) + require.Empty(t, tools) + require.NoError(t, <-done) + require.Empty(t, requireCustomToolsSnapshot(t, svc.browserRepl)) +} + func TestCustomWebMCPSnapshotRejectsDifferentRepl(t *testing.T) { svc := newBrowserReplSvc(t) ctx := context.Background() From 5e28c7b2e80eb8d437cd95566ccc171b45d0b7a9 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:56:57 +0000 Subject: [PATCH 17/18] Preserve custom tools across invocation timeouts --- server/cmd/api/api/custom_webmcp.go | 35 +++++++++++++++++++++++--- server/cmd/api/api/webmcp.go | 12 ++++++++- server/cmd/api/api/webmcp_test.go | 33 +++++++++++++++++++++++- server/e2e/e2e_custom_webmcp_test.go | 14 +++++++++++ server/lib/webmcpclient/client_test.go | 34 +++++++++++++++++++++++++ server/lib/webmcpclient/manager.go | 5 +++- 6 files changed, 126 insertions(+), 7 deletions(-) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index 42275f7d..f334db9b 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -113,16 +113,34 @@ func (m *browserReplManager) invokeCustomCDPTool(ctx context.Context, id, target if err != nil { return invocation, err } - code := fmt.Sprintf(`{ const result = JSON.stringify(await webmcp.invokeCustomCDPTool(%s, %s, %s)); - if (Buffer.byteLength(result) > %d) throw new Error('custom WebMCP output exceeds 240 KiB'); - repl.write(result); }`, encodedID, encodedTarget, encodedInput, maxCustomWebMCPResultBytes) - output, err := m.executeCustomWebMCPCodeWithTimeout(ctx, code, timeout) + code := fmt.Sprintf(`{ const controller = new AbortController(); + let timer; + try { + const result = JSON.stringify(await Promise.race([ + webmcp.invokeCustomCDPTool(%s, %s, %s, controller.signal), + new Promise((_, reject) => { + timer = setTimeout(() => { + const error = new Error('custom WebMCP invocation timed out'); + error.code = 'custom_tool_outcome_unknown'; + reject(error); + controller.abort(); + }, %d); + }), + ])); + if (Buffer.byteLength(result) > %d) throw new Error('custom WebMCP output exceeds 240 KiB'); + repl.write(result); + } finally { clearTimeout(timer); } + }`, encodedID, encodedTarget, encodedInput, timeout.Milliseconds(), maxCustomWebMCPResultBytes) + output, err := m.executeCustomWebMCPCodeRequest(ctx, code, timeout+browserReplResponseGrace, true) if err != nil { var executionErr *customWebMCPExecutionError if errors.As(err, &executionErr) { if executionErr.code == "custom_tool_not_found" { return invocation, webmcpclient.ErrToolNotFound } + if executionErr.code == "custom_tool_outcome_unknown" { + return invocation, webmcpclient.ErrOutcomeUnknown + } invocation.Status = "error" invocation.ErrorText = executionErr.Error() return invocation, nil @@ -145,6 +163,10 @@ func (m *browserReplManager) executeCustomWebMCPCode(ctx context.Context, code s } func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Context, code string, timeout time.Duration) (json.RawMessage, error) { + return m.executeCustomWebMCPCodeRequest(ctx, code, timeout, false) +} + +func (m *browserReplManager) executeCustomWebMCPCodeRequest(ctx context.Context, code string, timeout time.Duration, preserveOnRequestTimeout bool) (json.RawMessage, error) { if err := m.acquire(ctx); err != nil { return nil, &browserReplNotDispatchedError{cause: err} } @@ -168,6 +190,11 @@ func (m *browserReplManager) executeCustomWebMCPCodeWithTimeout(ctx context.Cont return nil, &browserReplNotDispatchedError{cause: fmt.Errorf("start Browser REPL: %w", err)} } replID := m.child.id + if preserveOnRequestTimeout { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(m.lifecycle, timeout+browserReplResponseGrace) + defer cancel() + } response, err := m.executeLocked(ctx, request, timeout) if err != nil { var notDispatched *browserReplNotDispatchedError diff --git a/server/cmd/api/api/webmcp.go b/server/cmd/api/api/webmcp.go index a169992a..bed24802 100644 --- a/server/cmd/api/api/webmcp.go +++ b/server/cmd/api/api/webmcp.go @@ -143,7 +143,17 @@ func (s *ApiService) InvokeWebMCPTool(ctx context.Context, request oapi.InvokeWe } else if definition, ok := definitions[customID]; !ok { err = webmcpclient.ErrToolNotFound } else if definition.Kind == "cdp" { - result, err = s.browserRepl.invokeCustomCDPTool(invokeCtx, customID, targetID, request.Body.Input, timeout) + if targetID == "" { + err = webmcpclient.ErrToolNotFound + } else if err = invokeCtx.Err(); err == nil { + deadline, _ := invokeCtx.Deadline() + remaining := time.Until(deadline) + if remaining < time.Millisecond { + err = context.DeadlineExceeded + } else { + result, err = s.browserRepl.invokeCustomCDPTool(invokeCtx, customID, targetID, request.Body.Input, remaining) + } + } } else { result, err = s.webmcp.Invoke(invokeCtx, request.Body.ToolRef, request.Body.Input) } diff --git a/server/cmd/api/api/webmcp_test.go b/server/cmd/api/api/webmcp_test.go index 8055b61e..22b062a4 100644 --- a/server/cmd/api/api/webmcp_test.go +++ b/server/cmd/api/api/webmcp_test.go @@ -22,6 +22,8 @@ type fakeWebMCPClient struct { invokeErr error toolRef string input map[string]any + customID string + targetID string } func (f *fakeWebMCPClient) Tools(_ context.Context) ([]webmcpclient.Tool, error) { @@ -29,7 +31,7 @@ func (f *fakeWebMCPClient) Tools(_ context.Context) ([]webmcpclient.Tool, error) } func (f *fakeWebMCPClient) CustomTool(_ context.Context, _ string) (string, string, error) { - return "", "", nil + return f.customID, f.targetID, nil } func (f *fakeWebMCPClient) Invoke(_ context.Context, toolRef string, input map[string]any) (webmcpclient.InvocationResult, error) { @@ -191,6 +193,35 @@ func TestInvokeWebMCPToolReturnsPageResult(t *testing.T) { require.Equal(t, true, body.Output.(map[string]any)["ok"]) } +func TestInvokePageCustomToolWithoutResolvedLocation(t *testing.T) { + manager := newBrowserReplManager() + writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{Id: "ct_abcdefghijklmnopqrstuvwx", Kind: "page"}}) + client := &fakeWebMCPClient{customID: "ct_abcdefghijklmnopqrstuvwx", result: webmcpclient.InvocationResult{ + InvocationID: "invocation-1", Status: "completed", Output: map[string]any{"ok": true}, + }} + service := &ApiService{webmcp: client, browserRepl: manager} + response, err := service.InvokeWebMCPTool(context.Background(), oapi.InvokeWebMCPToolRequestObject{ + Body: &oapi.WebMCPInvokeRequest{ToolRef: "wmcp_page", Input: map[string]any{}}, + }) + require.NoError(t, err) + require.Equal(t, oapi.WebMCPInvocationResultStatusCompleted, response.(oapi.InvokeWebMCPTool200JSONResponse).Status) + require.Equal(t, "wmcp_page", client.toolRef) +} + +func TestInvokeCDPCustomToolRequiresResolvedTarget(t *testing.T) { + manager := newBrowserReplManager() + writeCustomToolsSnapshot(t, manager, []oapi.CustomWebMCPDefinition{{Id: "ct_abcdefghijklmnopqrstuvwx", Kind: "cdp"}}) + client := &fakeWebMCPClient{customID: "ct_abcdefghijklmnopqrstuvwx"} + service := &ApiService{webmcp: client, browserRepl: manager} + response, err := service.InvokeWebMCPTool(context.Background(), oapi.InvokeWebMCPToolRequestObject{ + Body: &oapi.WebMCPInvokeRequest{ToolRef: "wmcp_cdp", Input: map[string]any{}}, + }) + require.NoError(t, err) + _, ok := response.(oapi.InvokeWebMCPTool404JSONResponse) + require.True(t, ok) + require.Empty(t, client.toolRef) +} + func TestInvokeWebMCPToolReturnsAwaitingSubmission(t *testing.T) { client := &fakeWebMCPClient{result: webmcpclient.InvocationResult{ InvocationID: "invocation-1", diff --git a/server/e2e/e2e_custom_webmcp_test.go b/server/e2e/e2e_custom_webmcp_test.go index 80012aa1..f6eaa076 100644 --- a/server/e2e/e2e_custom_webmcp_test.go +++ b/server/e2e/e2e_custom_webmcp_test.go @@ -225,4 +225,18 @@ func testCustomWebMCPInvokesAcrossNavigation(t *testing.T, ctx context.Context, require.NotNil(t, deadline.JSON200) require.True(t, deadline.JSON200.Success, "%s", deadline.Body) require.Nil(t, deadline.JSON200.ReplTerminated) + + oneSecond := 1 + timed, err := client.InvokeWebMCPToolWithResponse(ctx, instanceoapi.WebMCPInvokeRequest{ + ToolRef: slowRef, Input: map[string]any{}, TimeoutSec: &oneSecond, + }) + require.NoError(t, err) + require.Equal(t, http.StatusGatewayTimeout, timed.StatusCode(), "%s", timed.Body) + require.NotNil(t, timed.JSON504) + require.Equal(t, instanceoapi.OutcomeUnknown, timed.JSON504.Code) + listed, err := client.ListCustomWebMCPToolsWithResponse(ctx) + require.NoError(t, err) + require.Equal(t, http.StatusOK, listed.StatusCode(), "%s", listed.Body) + require.NotNil(t, listed.JSON200) + require.NotEmpty(t, listed.JSON200.Tools) } diff --git a/server/lib/webmcpclient/client_test.go b/server/lib/webmcpclient/client_test.go index c7ca4d2d..70cca9a6 100644 --- a/server/lib/webmcpclient/client_test.go +++ b/server/lib/webmcpclient/client_test.go @@ -425,6 +425,40 @@ func TestManagerTracksTabsOpenedAfterDiscovery(t *testing.T) { require.Equal(t, 3, popup.Source.TabID) } +func TestNativeInvocationDoesNotRequireLocation(t *testing.T) { + fake := newFakeCDP(t, false) + manager := NewManager(staticUpstream{url: fake.url}) + t.Cleanup(func() { _ = manager.Close() }) + tools, err := manager.Tools(context.Background()) + require.NoError(t, err) + var ref string + for _, tool := range tools { + if tool.Name == "merchant_tool" { + ref = tool.Ref + } + } + require.NotEmpty(t, ref) + + manager.connection.stateMu.Lock() + manager.connection.tools[ref].frameID = "unresolved-frame" + manager.connection.stateMu.Unlock() + id, targetID, err := manager.CustomTool(context.Background(), ref) + require.NoError(t, err) + require.Empty(t, id) + require.Empty(t, targetID) + result, err := manager.Invoke(context.Background(), ref, map[string]any{}) + require.NoError(t, err) + require.Equal(t, "Completed", result.Status) + + manager.connection.stateMu.Lock() + manager.connection.tools[ref].customID = "ct_abcdefghijklmnopqrstuvwx" + manager.connection.stateMu.Unlock() + id, targetID, err = manager.CustomTool(context.Background(), ref) + require.NoError(t, err) + require.Equal(t, "ct_abcdefghijklmnopqrstuvwx", id) + require.Empty(t, targetID) +} + func TestManagerReusesConnectionAndToolReferences(t *testing.T) { fake := newFakeCDP(t, false) manager := NewManager(staticUpstream{url: fake.url}) diff --git a/server/lib/webmcpclient/manager.go b/server/lib/webmcpclient/manager.go index 09b86904..93d2495f 100644 --- a/server/lib/webmcpclient/manager.go +++ b/server/lib/webmcpclient/manager.go @@ -65,9 +65,12 @@ func (m *Manager) CustomTool(ctx context.Context, toolRef string) (string, strin if !ok || !conn.enabledSessions[tool.sessionID] { return "", "", ErrToolNotFound } + if tool.customID == "" { + return "", "", nil + } location, ok := conn.surface.Resolve(tool.sessionID, tool.frameID) if !ok { - return "", "", ErrToolNotFound + return tool.customID, "", nil } return tool.customID, location.TargetID, nil } From 7f7f6d211c5e61a2fd4c6aae29f1f4dd04b7b27e Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:10:22 +0000 Subject: [PATCH 18/18] Honor the original custom tool invocation deadline --- server/cmd/api/api/custom_webmcp.go | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/server/cmd/api/api/custom_webmcp.go b/server/cmd/api/api/custom_webmcp.go index f334db9b..c8e56532 100644 --- a/server/cmd/api/api/custom_webmcp.go +++ b/server/cmd/api/api/custom_webmcp.go @@ -113,7 +113,17 @@ func (m *browserReplManager) invokeCustomCDPTool(ctx context.Context, id, target if err != nil { return invocation, err } - code := fmt.Sprintf(`{ const controller = new AbortController(); + deadline, ok := ctx.Deadline() + if !ok { + deadline = time.Now().Add(timeout) + } + code := fmt.Sprintf(`{ const remaining = %d - Date.now(); + if (remaining <= 0) { + const error = new Error('custom WebMCP invocation deadline elapsed before dispatch'); + error.code = 'custom_tool_not_dispatched'; + throw error; + } + const controller = new AbortController(); let timer; try { const result = JSON.stringify(await Promise.race([ @@ -124,17 +134,20 @@ func (m *browserReplManager) invokeCustomCDPTool(ctx context.Context, id, target error.code = 'custom_tool_outcome_unknown'; reject(error); controller.abort(); - }, %d); + }, remaining); }), ])); if (Buffer.byteLength(result) > %d) throw new Error('custom WebMCP output exceeds 240 KiB'); repl.write(result); } finally { clearTimeout(timer); } - }`, encodedID, encodedTarget, encodedInput, timeout.Milliseconds(), maxCustomWebMCPResultBytes) + }`, deadline.UnixMilli(), encodedID, encodedTarget, encodedInput, maxCustomWebMCPResultBytes) output, err := m.executeCustomWebMCPCodeRequest(ctx, code, timeout+browserReplResponseGrace, true) if err != nil { var executionErr *customWebMCPExecutionError if errors.As(err, &executionErr) { + if executionErr.code == "custom_tool_not_dispatched" { + return invocation, context.DeadlineExceeded + } if executionErr.code == "custom_tool_not_found" { return invocation, webmcpclient.ErrToolNotFound } @@ -191,6 +204,21 @@ func (m *browserReplManager) executeCustomWebMCPCodeRequest(ctx context.Context, } replID := m.child.id if preserveOnRequestTimeout { + if err := context.Cause(ctx); err != nil { + return nil, &browserReplNotDispatchedError{cause: err} + } + if deadline, ok := ctx.Deadline(); ok { + remaining := time.Until(deadline) + if remaining < time.Millisecond { + return nil, &browserReplNotDispatchedError{cause: context.DeadlineExceeded} + } + timeout = remaining + browserReplResponseGrace + request, err = prepareBrowserReplRequest(code, timeout) + if err != nil { + return nil, &customWebMCPExecutionError{message: err.Error()} + } + } + // Once admitted, leave time for the in-cell deadline to report without resetting the REPL. var cancel context.CancelFunc ctx, cancel = context.WithTimeout(m.lifecycle, timeout+browserReplResponseGrace) defer cancel()