hyperbrowser 1.10.0__tar.gz → 1.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/PKG-INFO +35 -1
  2. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/README.md +34 -0
  3. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/session.py +3 -0
  4. hyperbrowser-1.11.0/hyperbrowser/client/managers/async_manager/webmcp.py +98 -0
  5. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/session.py +3 -0
  6. hyperbrowser-1.11.0/hyperbrowser/client/managers/sync_manager/webmcp.py +98 -0
  7. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/__init__.py +29 -0
  8. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/session.py +6 -0
  9. hyperbrowser-1.11.0/hyperbrowser/models/webmcp.py +126 -0
  10. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/transport/async_transport.py +18 -2
  11. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/transport/base.py +8 -1
  12. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/transport/sync.py +18 -2
  13. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/__init__.py +9 -0
  14. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/session.py +1 -0
  15. hyperbrowser-1.11.0/hyperbrowser/types/webmcp.py +22 -0
  16. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/pyproject.toml +1 -1
  17. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/LICENSE +0 -0
  18. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/__init__.py +0 -0
  19. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/build_context.py +0 -0
  20. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/_request.py +0 -0
  21. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/async_client.py +0 -0
  22. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/base.py +0 -0
  23. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/__init__.py +0 -0
  24. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/browser_use.py +0 -0
  25. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/claude_computer_use.py +0 -0
  26. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/cua.py +0 -0
  27. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/gemini_computer_use.py +0 -0
  28. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/grok_computer_use.py +0 -0
  29. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/hyper_agent.py +0 -0
  30. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/jev_computer_use.py +0 -0
  31. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/agents/meta_computer_use.py +0 -0
  32. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/computer_action.py +0 -0
  33. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/crawl.py +0 -0
  34. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/extension.py +0 -0
  35. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/extract.py +0 -0
  36. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/profile.py +0 -0
  37. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandbox.py +0 -0
  38. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandboxes/__init__.py +0 -0
  39. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_files.py +0 -0
  40. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_processes.py +0 -0
  41. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_terminal.py +0 -0
  42. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/sandboxes/sandbox_transport.py +0 -0
  43. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/scrape.py +0 -0
  44. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/team.py +0 -0
  45. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/volume.py +0 -0
  46. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/web/__init__.py +0 -0
  47. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/web/batch_fetch.py +0 -0
  48. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/async_manager/web/crawl.py +0 -0
  49. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/__init__.py +0 -0
  50. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/dockerfile_analysis.py +0 -0
  51. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/dockerignore.py +0 -0
  52. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/image_build.py +0 -0
  53. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/image_resolution.py +0 -0
  54. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/process_output.py +0 -0
  55. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sandboxes/shared.py +0 -0
  56. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/__init__.py +0 -0
  57. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/browser_use.py +0 -0
  58. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/claude_computer_use.py +0 -0
  59. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/cua.py +0 -0
  60. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/gemini_computer_use.py +0 -0
  61. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/grok_computer_use.py +0 -0
  62. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/hyper_agent.py +0 -0
  63. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/jev_computer_use.py +0 -0
  64. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/agents/meta_computer_use.py +0 -0
  65. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/computer_action.py +0 -0
  66. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/crawl.py +0 -0
  67. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/extension.py +0 -0
  68. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/extract.py +0 -0
  69. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/profile.py +0 -0
  70. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandbox.py +0 -0
  71. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandboxes/__init__.py +0 -0
  72. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_files.py +0 -0
  73. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_processes.py +0 -0
  74. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_terminal.py +0 -0
  75. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/sandboxes/sandbox_transport.py +0 -0
  76. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/scrape.py +0 -0
  77. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/team.py +0 -0
  78. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/volume.py +0 -0
  79. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/web/__init__.py +0 -0
  80. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/web/batch_fetch.py +0 -0
  81. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/managers/sync_manager/web/crawl.py +0 -0
  82. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/client/sync.py +0 -0
  83. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/config.py +0 -0
  84. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/exceptions.py +0 -0
  85. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/image_builds.py +0 -0
  86. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/_parsers.py +0 -0
  87. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/browser_use.py +0 -0
  88. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/claude_computer_use.py +0 -0
  89. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/cua.py +0 -0
  90. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/gemini_computer_use.py +0 -0
  91. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/grok_computer_use.py +0 -0
  92. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/hyper_agent.py +0 -0
  93. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/jev_computer_use.py +0 -0
  94. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/agents/meta_computer_use.py +0 -0
  95. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/computer_action.py +0 -0
  96. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/consts.py +0 -0
  97. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/crawl.py +0 -0
  98. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/extension.py +0 -0
  99. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/extract.py +0 -0
  100. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/profile.py +0 -0
  101. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/sandbox.py +0 -0
  102. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/scrape.py +0 -0
  103. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/team.py +0 -0
  104. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/volume.py +0 -0
  105. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/batch_fetch.py +0 -0
  106. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/branding.py +0 -0
  107. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/common.py +0 -0
  108. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/crawl.py +0 -0
  109. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/fetch.py +0 -0
  110. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/models/web/search.py +0 -0
  111. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/py.typed +0 -0
  112. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/sandbox_common.py +0 -0
  113. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/tools/__init__.py +0 -0
  114. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/tools/anthropic.py +0 -0
  115. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/tools/openai.py +0 -0
  116. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/tools/schema.py +0 -0
  117. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/_json.py +0 -0
  118. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/agents.py +0 -0
  119. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/computer_action.py +0 -0
  120. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/crawl.py +0 -0
  121. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/extension.py +0 -0
  122. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/extract.py +0 -0
  123. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/profile.py +0 -0
  124. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/sandbox.py +0 -0
  125. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/scrape.py +0 -0
  126. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/volume.py +0 -0
  127. {hyperbrowser-1.10.0 → hyperbrowser-1.11.0}/hyperbrowser/types/web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperbrowser
3
- Version: 1.10.0
3
+ Version: 1.11.0
4
4
  Summary: Python SDK for hyperbrowser
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -62,6 +62,40 @@ Profile names must match `^[A-Za-z0-9._-]+$`.
62
62
 
63
63
  `base_url` and `HYPERBROWSER_BASE_URL` accept either `https://host` or `https://host/api`. The client normalizes both to the same control-plane base URL.
64
64
 
65
+ ## WebMCP page tools
66
+
67
+ Create a session with `{"enable_web_mcp": True}` (default: false), then navigate
68
+ to a page that exposes WebMCP tools using Playwright. With that session open:
69
+
70
+ ```python
71
+ discovery = client.sessions.webmcp.list_tools(session.id)
72
+ # Select a tool using its name, source page/frame, and input schema.
73
+ tool = next(tool for tool in discovery.tools if tool.name == "search")
74
+ result = client.sessions.webmcp.invoke(session.id, {
75
+ "tool_ref": tool.tool_ref,
76
+ "input": {"query": "flights to Tokyo"}, # Match the selected tool's schema.
77
+ })
78
+ print(result.status, result.output)
79
+ ```
80
+
81
+ For long-running tools or forms awaiting human submission, use
82
+ `client.sessions.webmcp.start(session_id, params)`, then
83
+ `client.sessions.webmcp.get_result(session_id, invocation_id, {"wait_seconds": 30})`
84
+ until the status is terminal. `client.sessions.webmcp.cancel(session_id, invocation_id)`
85
+ requests best-effort cancellation. Blocking invocation defaults to 60 seconds
86
+ (maximum 120); `start` defaults to 300 seconds (maximum 3,600) via `timeout_seconds`.
87
+ All five methods also work with `AsyncHyperbrowser` by adding `await`.
88
+
89
+ Discover tools again after navigation. Invocations never automatically retry;
90
+ losing a response can leave an unknown outcome. Handles are ephemeral and
91
+ terminal results expire after 10 minutes or earlier eviction. Check result
92
+ status and truncation metadata before using output. Tool input and output keys
93
+ are not renamed. Request TypedDicts are available in `hyperbrowser.types`, and
94
+ Pydantic request/response models in `hyperbrowser.models`.
95
+
96
+ See the [WebMCP guide](https://hyperbrowser.ai/docs/sessions/webmcp) for complete
97
+ examples, cancellation behavior, human submission, and limits.
98
+
65
99
  ## Usage
66
100
 
67
101
  Hyperbrowser 1.0 accepts plain dictionaries for request parameters. Method
@@ -34,6 +34,40 @@ Profile names must match `^[A-Za-z0-9._-]+$`.
34
34
 
35
35
  `base_url` and `HYPERBROWSER_BASE_URL` accept either `https://host` or `https://host/api`. The client normalizes both to the same control-plane base URL.
36
36
 
37
+ ## WebMCP page tools
38
+
39
+ Create a session with `{"enable_web_mcp": True}` (default: false), then navigate
40
+ to a page that exposes WebMCP tools using Playwright. With that session open:
41
+
42
+ ```python
43
+ discovery = client.sessions.webmcp.list_tools(session.id)
44
+ # Select a tool using its name, source page/frame, and input schema.
45
+ tool = next(tool for tool in discovery.tools if tool.name == "search")
46
+ result = client.sessions.webmcp.invoke(session.id, {
47
+ "tool_ref": tool.tool_ref,
48
+ "input": {"query": "flights to Tokyo"}, # Match the selected tool's schema.
49
+ })
50
+ print(result.status, result.output)
51
+ ```
52
+
53
+ For long-running tools or forms awaiting human submission, use
54
+ `client.sessions.webmcp.start(session_id, params)`, then
55
+ `client.sessions.webmcp.get_result(session_id, invocation_id, {"wait_seconds": 30})`
56
+ until the status is terminal. `client.sessions.webmcp.cancel(session_id, invocation_id)`
57
+ requests best-effort cancellation. Blocking invocation defaults to 60 seconds
58
+ (maximum 120); `start` defaults to 300 seconds (maximum 3,600) via `timeout_seconds`.
59
+ All five methods also work with `AsyncHyperbrowser` by adding `await`.
60
+
61
+ Discover tools again after navigation. Invocations never automatically retry;
62
+ losing a response can leave an unknown outcome. Handles are ephemeral and
63
+ terminal results expire after 10 minutes or earlier eviction. Check result
64
+ status and truncation metadata before using output. Tool input and output keys
65
+ are not renamed. Request TypedDicts are available in `hyperbrowser.types`, and
66
+ Pydantic request/response models in `hyperbrowser.models`.
67
+
68
+ See the [WebMCP guide](https://hyperbrowser.ai/docs/sessions/webmcp) for complete
69
+ examples, cancellation behavior, human submission, and limits.
70
+
37
71
  ## Usage
38
72
 
39
73
  Hyperbrowser 1.0 accepts plain dictionaries for request parameters. Method
@@ -1,4 +1,6 @@
1
1
  import warnings
2
+
3
+ from .webmcp import WebMCPManager
2
4
  from collections.abc import Mapping
3
5
  from typing import IO, List, Optional, Union, overload
4
6
 
@@ -68,6 +70,7 @@ class SessionManager:
68
70
  def __init__(self, client):
69
71
  self._client = client
70
72
  self.event_logs = SessionEventLogsManager(client)
73
+ self.webmcp = WebMCPManager(client)
71
74
 
72
75
  async def create(
73
76
  self,
@@ -0,0 +1,98 @@
1
+ from typing import Optional, Union
2
+ from urllib.parse import quote
3
+
4
+ from hyperbrowser.client._request import coerce_request, dump_request
5
+ from hyperbrowser.models.webmcp import (
6
+ WebMCPInvocation,
7
+ WebMCPInvokeParams,
8
+ WebMCPInvokeResult,
9
+ WebMCPResultParams,
10
+ WebMCPStartParams,
11
+ WebMCPToolsResponse,
12
+ )
13
+ from hyperbrowser.types.webmcp import (
14
+ WebMCPInvokeParams as WebMCPInvokeParamsDict,
15
+ WebMCPResultParams as WebMCPResultParamsDict,
16
+ WebMCPStartParams as WebMCPStartParamsDict,
17
+ )
18
+
19
+
20
+ class WebMCPManager:
21
+ """Discover and invoke page tools in sessions created with enable_web_mcp=True."""
22
+
23
+ def __init__(self, client):
24
+ self._client = client
25
+
26
+ def _url(self, session_id: str, suffix: str) -> str:
27
+ return self._client._build_url(
28
+ f"/session/{quote(session_id, safe='')}/webmcp{suffix}"
29
+ )
30
+
31
+ async def list_tools(self, session_id: str) -> WebMCPToolsResponse:
32
+ """Discover document-bound tools across the session's tabs and frames."""
33
+ response = await self._client.transport.get(
34
+ self._url(session_id, "/tools"), timeout=max(self._client.timeout, 40)
35
+ )
36
+ return WebMCPToolsResponse(**response.data)
37
+
38
+ async def invoke(
39
+ self,
40
+ session_id: str,
41
+ params: Union[WebMCPInvokeParamsDict, WebMCPInvokeParams],
42
+ ) -> WebMCPInvokeResult:
43
+ """Invoke once and wait. A form can return awaiting_submission with a handle.
44
+
45
+ This POST is never automatically retried: losing its response can leave the
46
+ outcome unknown, and repeating it could duplicate a side effect.
47
+ """
48
+ request = coerce_request(params, WebMCPInvokeParams)
49
+ response = await self._client.transport.post(
50
+ self._url(session_id, "/invoke"),
51
+ data=dump_request(request, WebMCPInvokeParams),
52
+ timeout=max(self._client.timeout, request.timeout_seconds + 35),
53
+ )
54
+ return WebMCPInvokeResult(**response.data)
55
+
56
+ async def start(
57
+ self,
58
+ session_id: str,
59
+ params: Union[WebMCPStartParamsDict, WebMCPStartParams],
60
+ ) -> WebMCPInvocation:
61
+ """Start once and return a handle; use get_result to observe its outcome."""
62
+ response = await self._client.transport.post(
63
+ self._url(session_id, "/invocations"),
64
+ data=dump_request(params, WebMCPStartParams),
65
+ timeout=max(self._client.timeout, 40),
66
+ )
67
+ return WebMCPInvocation(**response.data)
68
+
69
+ async def get_result(
70
+ self,
71
+ session_id: str,
72
+ invocation_id: str,
73
+ params: Optional[Union[WebMCPResultParamsDict, WebMCPResultParams]] = None,
74
+ ) -> WebMCPInvocation:
75
+ """Read or long-poll once. Running/awaiting_submission can remain pending.
76
+
77
+ Handles are in memory. Terminal results expire after 10 minutes and may
78
+ be evicted sooner; browser shutdown or receiver restart also loses them.
79
+ """
80
+ request = coerce_request(
81
+ params if params is not None else {}, WebMCPResultParams
82
+ )
83
+ response = await self._client.transport.get(
84
+ self._url(session_id, f"/invocations/{quote(invocation_id, safe='')}"),
85
+ params=dump_request(request, WebMCPResultParams),
86
+ timeout=max(self._client.timeout, request.wait_seconds + 20),
87
+ )
88
+ return WebMCPInvocation(**response.data)
89
+
90
+ async def cancel(self, session_id: str, invocation_id: str) -> WebMCPInvocation:
91
+ """Request best-effort cancellation. Completion can win; side effects persist."""
92
+ response = await self._client.transport.post(
93
+ self._url(
94
+ session_id, f"/invocations/{quote(invocation_id, safe='')}/cancel"
95
+ ),
96
+ timeout=max(self._client.timeout, 20),
97
+ )
98
+ return WebMCPInvocation(**response.data)
@@ -1,4 +1,6 @@
1
1
  import warnings
2
+
3
+ from .webmcp import WebMCPManager
2
4
  from collections.abc import Mapping
3
5
  from typing import IO, List, Optional, Union, overload
4
6
 
@@ -68,6 +70,7 @@ class SessionManager:
68
70
  def __init__(self, client):
69
71
  self._client = client
70
72
  self.event_logs = SessionEventLogsManager(client)
73
+ self.webmcp = WebMCPManager(client)
71
74
 
72
75
  def create(
73
76
  self,
@@ -0,0 +1,98 @@
1
+ from typing import Optional, Union
2
+ from urllib.parse import quote
3
+
4
+ from hyperbrowser.client._request import coerce_request, dump_request
5
+ from hyperbrowser.models.webmcp import (
6
+ WebMCPInvocation,
7
+ WebMCPInvokeParams,
8
+ WebMCPInvokeResult,
9
+ WebMCPResultParams,
10
+ WebMCPStartParams,
11
+ WebMCPToolsResponse,
12
+ )
13
+ from hyperbrowser.types.webmcp import (
14
+ WebMCPInvokeParams as WebMCPInvokeParamsDict,
15
+ WebMCPResultParams as WebMCPResultParamsDict,
16
+ WebMCPStartParams as WebMCPStartParamsDict,
17
+ )
18
+
19
+
20
+ class WebMCPManager:
21
+ """Discover and invoke page tools in sessions created with enable_web_mcp=True."""
22
+
23
+ def __init__(self, client):
24
+ self._client = client
25
+
26
+ def _url(self, session_id: str, suffix: str) -> str:
27
+ return self._client._build_url(
28
+ f"/session/{quote(session_id, safe='')}/webmcp{suffix}"
29
+ )
30
+
31
+ def list_tools(self, session_id: str) -> WebMCPToolsResponse:
32
+ """Discover document-bound tools across the session's tabs and frames."""
33
+ response = self._client.transport.get(
34
+ self._url(session_id, "/tools"), timeout=max(self._client.timeout, 40)
35
+ )
36
+ return WebMCPToolsResponse(**response.data)
37
+
38
+ def invoke(
39
+ self,
40
+ session_id: str,
41
+ params: Union[WebMCPInvokeParamsDict, WebMCPInvokeParams],
42
+ ) -> WebMCPInvokeResult:
43
+ """Invoke once and wait. A form can return awaiting_submission with a handle.
44
+
45
+ This POST is never automatically retried: losing its response can leave the
46
+ outcome unknown, and repeating it could duplicate a side effect.
47
+ """
48
+ request = coerce_request(params, WebMCPInvokeParams)
49
+ response = self._client.transport.post(
50
+ self._url(session_id, "/invoke"),
51
+ data=dump_request(request, WebMCPInvokeParams),
52
+ timeout=max(self._client.timeout, request.timeout_seconds + 35),
53
+ )
54
+ return WebMCPInvokeResult(**response.data)
55
+
56
+ def start(
57
+ self,
58
+ session_id: str,
59
+ params: Union[WebMCPStartParamsDict, WebMCPStartParams],
60
+ ) -> WebMCPInvocation:
61
+ """Start once and return a handle; use get_result to observe its outcome."""
62
+ response = self._client.transport.post(
63
+ self._url(session_id, "/invocations"),
64
+ data=dump_request(params, WebMCPStartParams),
65
+ timeout=max(self._client.timeout, 40),
66
+ )
67
+ return WebMCPInvocation(**response.data)
68
+
69
+ def get_result(
70
+ self,
71
+ session_id: str,
72
+ invocation_id: str,
73
+ params: Optional[Union[WebMCPResultParamsDict, WebMCPResultParams]] = None,
74
+ ) -> WebMCPInvocation:
75
+ """Read or long-poll once. Running/awaiting_submission can remain pending.
76
+
77
+ Handles are in memory. Terminal results expire after 10 minutes and may
78
+ be evicted sooner; browser shutdown or receiver restart also loses them.
79
+ """
80
+ request = coerce_request(
81
+ params if params is not None else {}, WebMCPResultParams
82
+ )
83
+ response = self._client.transport.get(
84
+ self._url(session_id, f"/invocations/{quote(invocation_id, safe='')}"),
85
+ params=dump_request(request, WebMCPResultParams),
86
+ timeout=max(self._client.timeout, request.wait_seconds + 20),
87
+ )
88
+ return WebMCPInvocation(**response.data)
89
+
90
+ def cancel(self, session_id: str, invocation_id: str) -> WebMCPInvocation:
91
+ """Request best-effort cancellation. Completion can win; side effects persist."""
92
+ response = self._client.transport.post(
93
+ self._url(
94
+ session_id, f"/invocations/{quote(invocation_id, safe='')}/cancel"
95
+ ),
96
+ timeout=max(self._client.timeout, 20),
97
+ )
98
+ return WebMCPInvocation(**response.data)
@@ -1,3 +1,18 @@
1
+ from .webmcp import (
2
+ WebMCPAnnotations,
3
+ WebMCPFrame,
4
+ WebMCPSource,
5
+ WebMCPTool,
6
+ WebMCPToolsResponse,
7
+ WebMCPInvokeParams,
8
+ WebMCPStartParams,
9
+ WebMCPResultParams,
10
+ WebMCPInvokeStatus,
11
+ WebMCPInvocationStatus,
12
+ WebMCPInvokeResult,
13
+ WebMCPInvocationError,
14
+ WebMCPInvocation,
15
+ )
1
16
  from .web.batch_fetch import (
2
17
  StartBatchFetchJobParams,
3
18
  StartBatchFetchJobResponse,
@@ -439,6 +454,20 @@ from .sandbox import (
439
454
  from .team import TeamCreditInfo
440
455
 
441
456
  __all__ = [
457
+ "WebMCPAnnotations",
458
+ "WebMCPFrame",
459
+ "WebMCPSource",
460
+ "WebMCPTool",
461
+ "WebMCPToolsResponse",
462
+ "WebMCPInvokeParams",
463
+ "WebMCPStartParams",
464
+ "WebMCPResultParams",
465
+ "WebMCPInvokeStatus",
466
+ "WebMCPInvocationStatus",
467
+ "WebMCPInvokeResult",
468
+ "WebMCPInvocationError",
469
+ "WebMCPInvocation",
470
+
442
471
  # consts
443
472
  "ISO639_1",
444
473
  "POLLING_ATTEMPTS",
@@ -129,6 +129,8 @@ class SessionLaunchState(BaseModel):
129
129
  populate_by_alias=True,
130
130
  )
131
131
 
132
+ enable_web_mcp: Optional[bool] = Field(default=None, alias="enableWebMcp")
133
+
132
134
  use_ultra_stealth: Optional[bool] = Field(default=None, alias="useUltraStealth")
133
135
  use_stealth: Optional[bool] = Field(default=None, alias="useStealth")
134
136
  use_proxy: Optional[bool] = Field(default=None, alias="useProxy")
@@ -411,6 +413,10 @@ class CreateSessionParams(BaseModel):
411
413
  populate_by_alias=True,
412
414
  )
413
415
 
416
+ enable_web_mcp: Optional[bool] = Field(
417
+ default=None, serialization_alias="enableWebMcp"
418
+ )
419
+
414
420
  use_ultra_stealth: bool = Field(
415
421
  default=False, serialization_alias="useUltraStealth"
416
422
  )
@@ -0,0 +1,126 @@
1
+ """WebMCP request models and page-tool discovery/invocation responses."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Literal, Optional
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ class WebMCPInvokeParams(BaseModel):
10
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
11
+
12
+ tool_ref: str = Field(min_length=1, max_length=4096, serialization_alias="toolRef")
13
+ input: Dict[str, Any] = Field(default_factory=dict)
14
+ timeout_seconds: int = Field(
15
+ default=60, ge=1, le=120, strict=True, serialization_alias="timeoutSeconds"
16
+ )
17
+
18
+
19
+ class WebMCPStartParams(WebMCPInvokeParams):
20
+ timeout_seconds: int = Field(
21
+ default=300, ge=1, le=3600, strict=True, serialization_alias="timeoutSeconds"
22
+ )
23
+
24
+
25
+ class WebMCPResultParams(BaseModel):
26
+ model_config = ConfigDict(populate_by_name=True, extra="forbid")
27
+
28
+ wait_seconds: int = Field(
29
+ default=0, ge=0, le=30, strict=True, serialization_alias="waitSeconds"
30
+ )
31
+
32
+
33
+ class WebMCPAnnotations(BaseModel):
34
+ model_config = ConfigDict(populate_by_name=True)
35
+
36
+ read_only: bool = Field(alias="readOnly")
37
+ untrusted_content: bool = Field(alias="untrustedContent")
38
+ consequential: bool
39
+ autosubmit: bool
40
+ destructive: Optional[bool] = None
41
+ idempotent: Optional[bool] = None
42
+ open_world: Optional[bool] = Field(default=None, alias="openWorld")
43
+
44
+
45
+ class WebMCPFrame(BaseModel):
46
+ model_config = ConfigDict(populate_by_name=True)
47
+
48
+ id: str
49
+ url: str
50
+ is_main_frame: bool = Field(alias="isMainFrame")
51
+
52
+
53
+ class WebMCPSource(BaseModel):
54
+ model_config = ConfigDict(populate_by_name=True)
55
+
56
+ provider: Literal["native", "polyfill"]
57
+ tab_id: str = Field(alias="tabId")
58
+ page_url: str = Field(alias="pageUrl")
59
+ page_title: str = Field(alias="pageTitle")
60
+ frame: WebMCPFrame
61
+
62
+
63
+ class WebMCPTool(BaseModel):
64
+ model_config = ConfigDict(populate_by_name=True)
65
+
66
+ tool_ref: str = Field(alias="toolRef")
67
+ name: str
68
+ title: Optional[str] = None
69
+ description: str
70
+ input_schema: Optional[Dict[str, Any]] = Field(default=None, alias="inputSchema")
71
+ output_schema: Optional[Dict[str, Any]] = Field(default=None, alias="outputSchema")
72
+ annotations: WebMCPAnnotations
73
+ declarative: bool
74
+ backend_node_id: Optional[int] = Field(default=None, alias="backendNodeId")
75
+ source: WebMCPSource
76
+
77
+
78
+ class WebMCPToolsResponse(BaseModel):
79
+ model_config = ConfigDict(populate_by_name=True)
80
+
81
+ tools: List[WebMCPTool]
82
+ native_supported: bool = Field(alias="nativeSupported")
83
+ truncated: bool
84
+
85
+
86
+ WebMCPInvokeStatus = Literal["completed", "error", "canceled", "awaiting_submission"]
87
+ WebMCPInvocationStatus = Literal[
88
+ "running",
89
+ "awaiting_submission",
90
+ "completed",
91
+ "error",
92
+ "canceled",
93
+ "outcome_unknown",
94
+ ]
95
+
96
+
97
+ class WebMCPInvokeResult(BaseModel):
98
+ model_config = ConfigDict(populate_by_name=True)
99
+
100
+ invocation_id: Optional[str] = Field(default=None, alias="invocationId")
101
+ status: WebMCPInvokeStatus
102
+ output: Any = None
103
+ error_text: Optional[str] = Field(default=None, alias="errorText")
104
+ output_bytes: int = Field(alias="outputBytes")
105
+ output_truncated: bool = Field(default=False, alias="outputTruncated")
106
+ output_preview: Optional[str] = Field(default=None, alias="outputPreview")
107
+ untrusted_content: bool = Field(alias="untrustedContent")
108
+ duration_ms: int = Field(alias="durationMs")
109
+
110
+
111
+ class WebMCPInvocationError(BaseModel):
112
+ code: str
113
+ message: str
114
+
115
+
116
+ class WebMCPInvocation(BaseModel):
117
+ model_config = ConfigDict(populate_by_name=True)
118
+
119
+ invocation_id: str = Field(alias="invocationId")
120
+ tool_ref: str = Field(alias="toolRef")
121
+ status: WebMCPInvocationStatus
122
+ cancellation_requested: bool = Field(alias="cancellationRequested")
123
+ created_at: datetime = Field(alias="createdAt")
124
+ expires_at: Optional[datetime] = Field(default=None, alias="expiresAt")
125
+ result: Optional[WebMCPInvokeResult] = None
126
+ error: Optional[WebMCPInvocationError] = None
@@ -64,6 +64,7 @@ class AsyncTransport(TransportStrategy):
64
64
  )
65
65
  return APIResponse.from_status(response.status_code)
66
66
  except httpx.HTTPStatusError as e:
67
+ error_data = None
67
68
  try:
68
69
  error_data = response.json()
69
70
  message = error_data.get("message") or error_data.get("error") or str(e)
@@ -71,6 +72,13 @@ class AsyncTransport(TransportStrategy):
71
72
  message = str(e)
72
73
  raise HyperbrowserError(
73
74
  message,
75
+ code=(
76
+ error_data.get("code")
77
+ if isinstance(error_data, dict)
78
+ and isinstance(error_data.get("code"), str)
79
+ else None
80
+ ),
81
+ details=error_data,
74
82
  status_code=response.status_code,
75
83
  response=response,
76
84
  original_error=e,
@@ -108,15 +116,23 @@ class AsyncTransport(TransportStrategy):
108
116
  )
109
117
 
110
118
  async def get(
111
- self, url: str, params: Optional[dict] = None, follow_redirects: bool = False
119
+ self,
120
+ url: str,
121
+ params: Optional[dict] = None,
122
+ follow_redirects: bool = False,
123
+ *,
124
+ timeout: Optional[float] = None,
112
125
  ) -> APIResponse:
113
126
  if params:
114
127
  params = {k: v for k, v in params.items() if v is not None}
128
+ kwargs = {}
129
+ if timeout is not None:
130
+ kwargs["timeout"] = timeout
115
131
  failed_attempt = 1
116
132
  while True:
117
133
  try:
118
134
  response = await self.client.get(
119
- url, params=params, follow_redirects=follow_redirects
135
+ url, params=params, follow_redirects=follow_redirects, **kwargs
120
136
  )
121
137
  return await self._handle_response(response)
122
138
  except BaseException as cause:
@@ -55,7 +55,14 @@ class TransportStrategy(ABC):
55
55
  pass
56
56
 
57
57
  @abstractmethod
58
- def get(self, url: str, params: Optional[dict] = None) -> APIResponse:
58
+ def get(
59
+ self,
60
+ url: str,
61
+ params: Optional[dict] = None,
62
+ follow_redirects: bool = False,
63
+ *,
64
+ timeout: Optional[float] = None,
65
+ ) -> APIResponse:
59
66
  pass
60
67
 
61
68
  @abstractmethod
@@ -42,6 +42,7 @@ class SyncTransport(TransportStrategy):
42
42
  )
43
43
  return APIResponse.from_status(response.status_code)
44
44
  except httpx.HTTPStatusError as e:
45
+ error_data = None
45
46
  try:
46
47
  error_data = response.json()
47
48
  message = error_data.get("message") or error_data.get("error") or str(e)
@@ -49,6 +50,13 @@ class SyncTransport(TransportStrategy):
49
50
  message = str(e)
50
51
  raise HyperbrowserError(
51
52
  message,
53
+ code=(
54
+ error_data.get("code")
55
+ if isinstance(error_data, dict)
56
+ and isinstance(error_data.get("code"), str)
57
+ else None
58
+ ),
59
+ details=error_data,
52
60
  status_code=response.status_code,
53
61
  response=response,
54
62
  original_error=e,
@@ -89,15 +97,23 @@ class SyncTransport(TransportStrategy):
89
97
  )
90
98
 
91
99
  def get(
92
- self, url: str, params: Optional[dict] = None, follow_redirects: bool = False
100
+ self,
101
+ url: str,
102
+ params: Optional[dict] = None,
103
+ follow_redirects: bool = False,
104
+ *,
105
+ timeout: Optional[float] = None,
93
106
  ) -> APIResponse:
94
107
  if params:
95
108
  params = {k: v for k, v in params.items() if v is not None}
109
+ kwargs = {}
110
+ if timeout is not None:
111
+ kwargs["timeout"] = timeout
96
112
  failed_attempt = 1
97
113
  while True:
98
114
  try:
99
115
  response = self.client.get(
100
- url, params=params, follow_redirects=follow_redirects
116
+ url, params=params, follow_redirects=follow_redirects, **kwargs
101
117
  )
102
118
  return self._handle_response(response)
103
119
  except BaseException as cause:
@@ -5,6 +5,11 @@ known request keys to their wire aliases while leaving user-owned mappings, such
5
5
  as JSON Schema objects, untouched.
6
6
  """
7
7
 
8
+ from .webmcp import (
9
+ WebMCPInvokeParams,
10
+ WebMCPStartParams,
11
+ WebMCPResultParams,
12
+ )
8
13
  from ._json import (
9
14
  JSONSchema,
10
15
  JSONSchemaInput,
@@ -178,6 +183,10 @@ from .web import (
178
183
 
179
184
 
180
185
  __all__ = [
186
+ "WebMCPInvokeParams",
187
+ "WebMCPStartParams",
188
+ "WebMCPResultParams",
189
+
181
190
  "JSONSchema",
182
191
  "JSONSchemaInput",
183
192
  "JSONSchemaObject",
@@ -117,6 +117,7 @@ class StartSessionFromSnapshotParams(TypedDict):
117
117
  class CreateSessionParams(TypedDict, total=False):
118
118
  """Configuration for creating a browser session."""
119
119
 
120
+ enable_web_mcp: Optional[bool]
120
121
  use_ultra_stealth: bool
121
122
  use_stealth: bool
122
123
  use_proxy: bool
@@ -0,0 +1,22 @@
1
+ """Typed dictionary requests for session WebMCP tools."""
2
+
3
+ from typing import Any, Dict
4
+ from typing_extensions import Required, TypedDict
5
+
6
+
7
+ class WebMCPInvokeParams(TypedDict, total=False):
8
+ """Blocking invocation; timeout_seconds is 1–120 (default 60)."""
9
+
10
+ tool_ref: Required[str]
11
+ input: Dict[str, Any]
12
+ timeout_seconds: int
13
+
14
+
15
+ class WebMCPStartParams(WebMCPInvokeParams):
16
+ """Start an invocation; timeout_seconds is 1–3600 (default 300)."""
17
+
18
+
19
+ class WebMCPResultParams(TypedDict, total=False):
20
+ """Wait at most 0–30 seconds (default 0) for an invocation result."""
21
+
22
+ wait_seconds: int
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "hyperbrowser"
3
- version = "1.10.0"
3
+ version = "1.11.0"
4
4
  description = "Python SDK for hyperbrowser"
5
5
  authors = ["Nikhil Shahi <nshahi1998@gmail.com>"]
6
6
  license = "MIT"
File without changes