@raultov/browser-control-mcp 0.0.0-stage → 0.2.0-beta.1
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.
- package/.gitignore +2 -0
- package/CHANGELOG.md +299 -0
- package/LICENSE +21 -0
- package/README.md +510 -2
- package/binary-install.js +348 -0
- package/binary.js +124 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +52 -0
- package/package.json +97 -3
- package/run-browser-control-mcp.js +4 -0
package/.gitignore
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `browser-control-mcp` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
`browser-control-mcp` is the successor of `chrome-debug-mcp` 1.5.6 and starts
|
|
9
|
+
from a copy of its code: entries describe the differences from that release.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.2.0-beta.1] - 2026-10-13
|
|
14
|
+
|
|
15
|
+
Public **BETA**. Chrome and Firefox now answer the same replies; several are
|
|
16
|
+
incompatible with 0.1.x (marked **Breaking**; see the README's "Migration
|
|
17
|
+
from 0.1.x to 0.2.0").
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Breaking:** `navigate` and `reload` answer the same shape on Chrome and
|
|
22
|
+
Firefox once the page fired `load` (at most 30 s): `{url, tab_id, browser,
|
|
23
|
+
notes?}`, `url` being the final URL after redirects and `browser` the
|
|
24
|
+
protocol's ids (Chrome used to answer the raw `Page.navigate` reply without
|
|
25
|
+
waiting, and `{}` on reload). `open_tab` reports the final URL too, and on
|
|
26
|
+
Firefox it now creates the tab blank and navigates once it is registered,
|
|
27
|
+
as on Chrome, so the page's first events are captured.
|
|
28
|
+
- **Breaking:** a network error fails `navigate` and `open_tab` on both
|
|
29
|
+
browsers with `Navigation failed: <browser's code> (<url>)` (Chrome used to
|
|
30
|
+
report success with `errorText` inside, or a tab on `chrome-error://`); from
|
|
31
|
+
`open_tab` the error names the tab, which stays open.
|
|
32
|
+
- **Breaking:** `evaluate_js` answers `{value, type, subtype?, description?}`
|
|
33
|
+
on both browsers (Chrome used to wrap the value in the CDP `RemoteObject`,
|
|
34
|
+
Firefox to return the bare value). Values without a JSON form (functions,
|
|
35
|
+
DOM nodes, symbols, `NaN`, bigints, maps...) have `value: null` and a
|
|
36
|
+
`description`; on Chrome they no longer fail with "Object couldn't be
|
|
37
|
+
returned by value" or answer `{}` (array-likes such as `NodeList` are
|
|
38
|
+
described, e.g. `NodeList(6)`, as on Firefox). A thrown exception fails
|
|
39
|
+
with the same wording on both: what was thrown plus its 1-based line and
|
|
40
|
+
column.
|
|
41
|
+
- **Breaking:** the debugger tools answer the same shapes on both browsers,
|
|
42
|
+
with 0-based lines everywhere: `set_breakpoint` returns an MCP id
|
|
43
|
+
(`bp-N`, also on Chrome instead of the CDP id) with the bound location and
|
|
44
|
+
`resolved`; `remove_breakpoint` takes that id and answers `{removed}`;
|
|
45
|
+
`step_over` and `resume` answer `{paused, location}` (Firefox's step
|
|
46
|
+
location was 1-based; Chrome reported none); `evaluate_on_call_frame`
|
|
47
|
+
answers the `evaluate_js` shape, and an exception in the frame is an error
|
|
48
|
+
on Chrome too (`Evaluation threw: ...`; it used to be a success carrying
|
|
49
|
+
`exceptionDetails`). `step_over` without a pause fails with "the debugger
|
|
50
|
+
is not paused" on both.
|
|
51
|
+
- **Breaking:** `get_console_logs` answers `{messages, total, truncated,
|
|
52
|
+
notes?}` instead of a bare array, returns at most `limit` messages (new
|
|
53
|
+
argument, default 200, the most recent ones), and `clear` removes only the
|
|
54
|
+
returned messages (it used to empty the whole buffer, even messages a
|
|
55
|
+
`level_filter` had hidden).
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Partial `get_performance_metrics` and `profile_page_performance` on
|
|
60
|
+
Firefox, from the standard Web Performance APIs: `Nodes`, `Frames`,
|
|
61
|
+
`Resources` and `TransferSize`; FCP, LCP, DCL and Load (optionally after a
|
|
62
|
+
reload, cache bypassed with `disable_cache`). What Firefox cannot give (JS
|
|
63
|
+
heap, layout counts, long tasks, total blocking time) is `null` or listed
|
|
64
|
+
in a note, never a silent empty value.
|
|
65
|
+
- Firefox WebSocket frames in `get_network_logs`, and the console messages
|
|
66
|
+
WebDriver BiDi does not report (script errors with file and line, cookie
|
|
67
|
+
and security warnings, CSS warnings, platform messages) in
|
|
68
|
+
`get_console_logs`, from the RDP watcher of each tab's debugger, in the
|
|
69
|
+
same shapes as Chrome. Each tab's debugger is attached when the session
|
|
70
|
+
starts, when `open_tab` creates the tab, or at the first log call on a tab
|
|
71
|
+
the page opened, matched by its position in BiDi's and RDP's tab lists
|
|
72
|
+
(checked against their URLs), without bringing it to the front. Needs the
|
|
73
|
+
Firefox debugger port (default for a launched Firefox). Messages both feeds report are kept once; console
|
|
74
|
+
`source` labels say what a Firefox message is (`javascript`, `css`,
|
|
75
|
+
`security`, `network`, `other`).
|
|
76
|
+
- `--window-size <W>x<H>` (default `1920x1080`): launched Chrome and Firefox
|
|
77
|
+
get the same window size, headless too (they used to pick 921x648 and
|
|
78
|
+
1280x887). Attached browsers keep their window.
|
|
79
|
+
- A tab's console buffer is cleared when the tab moves to another site
|
|
80
|
+
(registrable domain, via the `psl` crate's embedded Public Suffix List)
|
|
81
|
+
and kept across subdomains of one site, reloads and same-page
|
|
82
|
+
navigations; the next `get_console_logs` says what was cleared.
|
|
83
|
+
|
|
84
|
+
- Lifecycle crates bumped: `cdp-browser-lite` 0.4.2 and `bidi-browser-lite`
|
|
85
|
+
0.1.4 (`bidi-lite` 0.1.2). Their mocks now record wire parameters, so the
|
|
86
|
+
Firefox page, input, network and debugger tools and the Chrome domain
|
|
87
|
+
setup are covered by hermetic tests instead of only by the E2E smoke.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- Chrome `get_performance_metrics` returned `{}`: every tab now enables the
|
|
92
|
+
Performance domain when it is registered, the tool enables it on a session
|
|
93
|
+
that missed it, and an empty metric list is an error instead of `{}`.
|
|
94
|
+
- The "Chrome not found, use Firefox?" hint no longer ends the library's
|
|
95
|
+
sentence with two periods.
|
|
96
|
+
|
|
97
|
+
## [0.1.1-beta.1] - 2026-10-12
|
|
98
|
+
|
|
99
|
+
Public **BETA**: known bugs and Chrome/Firefox differences are listed in the
|
|
100
|
+
README ("Limitations and TODO"). The GitHub Release, the shell/PowerShell
|
|
101
|
+
installers, the MSI and the npm package start with this version (0.1.0 was
|
|
102
|
+
only published to crates.io).
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
- README: per-platform installation section (shell and PowerShell
|
|
106
|
+
installers, npm/npx, Homebrew, MSI, Cargo/binstall, manual download,
|
|
107
|
+
Docker, source) and a one-prompt install for AI agents.
|
|
108
|
+
- Releases of beta versions are published as the latest GitHub Release and
|
|
109
|
+
with the npm `latest` tag, so `releases/latest/download/...` installers
|
|
110
|
+
and `npm install -g @raultov/browser-control-mcp` get them.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
- A Windows-only unit test failure (path separator in the cookie-import
|
|
114
|
+
error assertion).
|
|
115
|
+
|
|
116
|
+
## [0.1.0] - 2026-10-12
|
|
117
|
+
|
|
118
|
+
First public release, **BETA**: known bugs and Chrome/Firefox differences are
|
|
119
|
+
listed in the README ("Limitations and TODO").
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
- When Chrome is not installed and a Firefox is, the launch error says so
|
|
123
|
+
and tells the agent to ask the user before using `"browser": "firefox"`.
|
|
124
|
+
- E2E smoke over stdio MCP against real, visible Chrome and Firefox
|
|
125
|
+
(`make e2e`: launched and attach modes, SIGTERM and relaunch), with
|
|
126
|
+
`tests/chrome.sh` and `tests/firefox.sh` for dedicated test browsers.
|
|
127
|
+
- Project scaffolding: quality gates (`make check`: format, clippy with
|
|
128
|
+
warnings as errors, tests, docs, duplication, packaging and version
|
|
129
|
+
consistency), CI on Linux, macOS and Windows, `AGENTS.md`, MIT licence.
|
|
130
|
+
- The server code of chrome-debug-mcp 1.5.6 (35 tools, Chrome over CDP),
|
|
131
|
+
copied without its history and renamed: crate and binary
|
|
132
|
+
`browser-control-mcp`, server title "Browser Control MCP".
|
|
133
|
+
|
|
134
|
+
- `--browser <chrome|firefox>` selects the browser of the default instance
|
|
135
|
+
and `open_instance` takes a `browser` argument (Firefox lands in later
|
|
136
|
+
tasks; until then a Firefox instance answers "not implemented yet").
|
|
137
|
+
- Every tool declares what it needs; a tool an instance's browser cannot
|
|
138
|
+
serve fails with an error naming the tool and the browser, before anything
|
|
139
|
+
is sent. `list_instances` shows each instance's `browser` and
|
|
140
|
+
`capabilities`.
|
|
141
|
+
- On exit (end of stdin, Ctrl-C, SIGTERM on Unix) the server stops every
|
|
142
|
+
browser it started; the exit code reports instances that did not stop.
|
|
143
|
+
|
|
144
|
+
- **Firefox support** over WebDriver BiDi (`--browser firefox`, or
|
|
145
|
+
`open_instance` with `browser: "firefox"`): tabs, `navigate`, `reload`,
|
|
146
|
+
`evaluate_js` (values as plain JSON), `inspect_dom`, `scroll`,
|
|
147
|
+
`click_element` (real pointer input), `fill_input` (real key input),
|
|
148
|
+
`capture_screenshot`, `get_console_logs`, `get_network_logs` (with response
|
|
149
|
+
bodies; no WebSocket frames) and `get_custom_events`; instance tools
|
|
150
|
+
(`open_instance`, `close_instance`, `restart_browser`, `stop_browser`,
|
|
151
|
+
`list_instances` with the Firefox version). Events of iframes are kept with
|
|
152
|
+
their tab. Firefox instances are launched with an ephemeral profile, or
|
|
153
|
+
attached with `--firefox-port`.
|
|
154
|
+
- **JavaScript debugging on Firefox** over the Firefox Remote Debugging
|
|
155
|
+
Protocol: `set_breakpoint` (by `url`), `remove_breakpoint`, `resume`,
|
|
156
|
+
`step_over` (reports the new location), `evaluate_on_call_frame` and
|
|
157
|
+
`search_scripts`. The debugger port is on by default for launched Firefox
|
|
158
|
+
instances; `--no-firefox-debugger` turns it off. `pause_on_load` is Chrome
|
|
159
|
+
only for now.
|
|
160
|
+
- Firefox flags: `--firefox-path`, `--firefox-port`, `--firefox-rdp-port`,
|
|
161
|
+
`--no-firefox-debugger`. `--enable-automation` is reported as ignored on
|
|
162
|
+
Firefox instances by `list_instances`.
|
|
163
|
+
|
|
164
|
+
- `send_bidi_command` (raw WebDriver BiDi; refuses `session.new`,
|
|
165
|
+
`session.end`, `browser.close` and removing the server's own subscription;
|
|
166
|
+
its `session.subscribe` events feed `get_custom_events`) and
|
|
167
|
+
`send_rdp_request` (raw Firefox RDP, with `thread`/`console`/`root`
|
|
168
|
+
shortcuts). Firefox only.
|
|
169
|
+
- `enable_proxy_auth` on Firefox (`network.authRequired` →
|
|
170
|
+
`network.continueWithAuth`, credentials only for 407). **Untested against a
|
|
171
|
+
real authenticating proxy.**
|
|
172
|
+
|
|
173
|
+
- Distribution: cargo-dist release workflow (shell, PowerShell, npm
|
|
174
|
+
`@raultov/browser-control-mcp`, Homebrew, MSI with its own GUIDs) behind a
|
|
175
|
+
preflight that refuses a tag not matching `Cargo.toml`/`package.json` or
|
|
176
|
+
without a CHANGELOG section; release notes are that section. Docker image
|
|
177
|
+
with Google Chrome and Firefox (amd64). glama.json exposes `--browser`.
|
|
178
|
+
|
|
179
|
+
### Changed
|
|
180
|
+
- `cdp-browser-lite`'s test mocks are no longer linked into the binary.
|
|
181
|
+
- Dependencies updated to their latest releases; unused test dependencies
|
|
182
|
+
(`hyper`, `hyper-util`, `http-body-util`, `futures-util`,
|
|
183
|
+
`tokio-tungstenite`) removed.
|
|
184
|
+
- `rust-mcp-sdk` stays on 1.x (MCP 2025-11-25) with only its `server`,
|
|
185
|
+
`macros` and `stdio` features: the binary no longer links the SDK's HTTP,
|
|
186
|
+
SSE and OAuth client stack.
|
|
187
|
+
|
|
188
|
+
- `navigate`, `reload`, `evaluate_js`, `evaluate_on_call_frame`,
|
|
189
|
+
`set_breakpoint`, `remove_breakpoint` and `send_cdp_command` return the
|
|
190
|
+
protocol reply as pretty JSON after their usual prefix, instead of the Rust
|
|
191
|
+
`Debug` of the whole message (`WsResponse { .. }`).
|
|
192
|
+
|
|
193
|
+
- **Renamed tools:** `restart_chrome` → `restart_browser`, `stop_chrome` →
|
|
194
|
+
`stop_browser`. Calling an old name returns an error naming the new one.
|
|
195
|
+
- Generated instance ids carry the browser name (`chrome-2`, `firefox-3`).
|
|
196
|
+
- Server title "Browser Control MCP"; new description and instructions that
|
|
197
|
+
cover both browsers.
|
|
198
|
+
|
|
199
|
+
- Tool descriptions say which tools are Chrome only and no longer mention
|
|
200
|
+
tools that do not exist (`pause_on_exceptions`, `step_out`);
|
|
201
|
+
`remove_breakpoint` no longer claims the page must be paused.
|
|
202
|
+
|
|
203
|
+
### Fixed
|
|
204
|
+
- `stop_browser` / `restart_browser` on Chrome could deadlock with a tool
|
|
205
|
+
making the instance's first connection (two locks taken in opposite order).
|
|
206
|
+
- Chrome `open_instance` checks the instance limit and the label before
|
|
207
|
+
launching: a refused instance no longer leaves an untracked Chrome running.
|
|
208
|
+
- No instance can take the id `default` (it hijacked the default instance).
|
|
209
|
+
- `open_tab` checks the tab limit and the label before creating the page,
|
|
210
|
+
keeps labels unique when target discovery registered the page first, and
|
|
211
|
+
closes the page if it still cannot register it (Chrome and Firefox).
|
|
212
|
+
- `close_tab` forgets a tab only once the browser closed it (Chrome and
|
|
213
|
+
Firefox).
|
|
214
|
+
- Firefox: a lost WebSocket no longer leaves a dead event pump (empty console
|
|
215
|
+
and network logs): the next tool starts a new session.
|
|
216
|
+
- Firefox: tab ids are not reused after `restart_browser`; the iframe map
|
|
217
|
+
forgets a destroyed context's whole subtree; debuggers of tabs closed by the
|
|
218
|
+
page are dropped.
|
|
219
|
+
- Firefox `enable_proxy_auth` subscribes to `network.authRequired` before the
|
|
220
|
+
intercept exists, so no challenge is missed (and lags are logged).
|
|
221
|
+
- Firefox `set_breakpoint` with `line_number` 4294967295 is an argument error
|
|
222
|
+
instead of an overflow.
|
|
223
|
+
- Shutdown: tool calls are refused once the server is stopping, and a
|
|
224
|
+
debugger tool in flight no longer delays stopping Firefox past its budget.
|
|
225
|
+
- The server exits on SIGTERM/Ctrl-C: it used to stop its browsers and then
|
|
226
|
+
hang, waiting for a stdin read that only returns on input.
|
|
227
|
+
- Chrome `profile_page_performance` no longer times out at random on tabs
|
|
228
|
+
opened with `open_tab`: when target discovery and `open_tab` both attached
|
|
229
|
+
the page, the second registration replaced the tab's listeners with ones on
|
|
230
|
+
a session nobody sent commands to.
|
|
231
|
+
- Chrome `open_tab` with a URL records the page's first requests: the tab is
|
|
232
|
+
created blank and navigated once its domains are enabled.
|
|
233
|
+
- `profile_page_performance` reports a failed `Tracing.end` instead of a
|
|
234
|
+
timeout.
|
|
235
|
+
- `reload` right after `navigate` on Chrome no longer fails with "Not attached
|
|
236
|
+
to an active page": the transient error is retried once after 500 ms.
|
|
237
|
+
- `inspect_dom` no longer crashes the server when the context window around a
|
|
238
|
+
match falls inside a multi-byte character.
|
|
239
|
+
|
|
240
|
+
- The server can no longer panic on a poisoned lock or an unexpected value:
|
|
241
|
+
every `unwrap`/`expect` outside tests was removed; the instance registry
|
|
242
|
+
uses a single lock (two locks taken in different orders could deadlock).
|
|
243
|
+
- Custom-domain listeners started by `send_cdp_command` on a tab are stopped
|
|
244
|
+
when the tab closes (a reference cycle kept them running forever), and no
|
|
245
|
+
longer leak one static string per domain name.
|
|
246
|
+
- Per-tab state fed by the browser is bounded: at most 2,000 parsed scripts,
|
|
247
|
+
100 WebSocket connections (with their frames) and 1,000 WebMCP
|
|
248
|
+
invocations; the oldest are forgotten first.
|
|
249
|
+
|
|
250
|
+
- `search_scripts` / script cache: Chrome's numeric `startLine`/`startColumn`
|
|
251
|
+
were read as strings and always stored as 0.
|
|
252
|
+
- `evaluate_on_call_frame` no longer uses a stale frame after the page
|
|
253
|
+
resumed (`Debugger.resumed` clears it).
|
|
254
|
+
- `pause_on_load` pauses once: the injected `debugger;` script is removed on
|
|
255
|
+
the next `resume` instead of pausing every later navigation.
|
|
256
|
+
- `get_custom_events` with `limit: 0` returns every event (it returned none).
|
|
257
|
+
- `get_network_logs`: a redirect keeps one entry per request id, with the
|
|
258
|
+
final URL and a `redirects` list.
|
|
259
|
+
- `profile_page_performance` restores the cache and its completion channel
|
|
260
|
+
when tracing fails or times out, and keeps the top 5 long tasks its
|
|
261
|
+
description promises (it kept 10).
|
|
262
|
+
- `fill_input` replaces the field's content, as its description says.
|
|
263
|
+
- Closing the active tab activates the most recently used tab (it picked an
|
|
264
|
+
arbitrary one); `list_tabs` reports each tab's current URL; a tab opened
|
|
265
|
+
with `open_tab` can no longer be registered twice.
|
|
266
|
+
- The session-level WebMCP listener starts before `WebMCP.enable`, so the
|
|
267
|
+
initial tool list is not missed.
|
|
268
|
+
|
|
269
|
+
- A Firefox launched by the server no longer starts on `about:home`, where
|
|
270
|
+
BiDi refuses page scripts: its first tab is moved to `about:blank`.
|
|
271
|
+
|
|
272
|
+
### Security
|
|
273
|
+
- `--local` treats `192.168.x.x` only as an IP address: a domain name such
|
|
274
|
+
as `192.168.attacker.com` is no longer accepted as local.
|
|
275
|
+
- `--local` also covers RDP `navigateTo` in `send_rdp_request`.
|
|
276
|
+
- `send_bidi_command` refuses `session.unsubscribe` of the events the server
|
|
277
|
+
listens to (by event or module name), not only of its subscription id.
|
|
278
|
+
- The start-up log no longer prints the proxy password, username or a
|
|
279
|
+
`user:pass@` embedded in `--proxy-server` (chrome-debug-mcp printed every
|
|
280
|
+
argument). Logs now go through `tracing` to stderr (`RUST_LOG`, default
|
|
281
|
+
`info`) and carry ids, method names and error kinds, never payloads;
|
|
282
|
+
`cdp-lite`'s payload logging is capped at `info` whatever `RUST_LOG` says.
|
|
283
|
+
- `enable_proxy_auth` answers only proxy (407) challenges; a site's 401 gets
|
|
284
|
+
the browser's default handling instead of the proxy credentials.
|
|
285
|
+
- `enable_proxy_auth` checks the credentials and `--local` (pre-warm URL)
|
|
286
|
+
before enabling Fetch, so a refused call no longer leaves every request
|
|
287
|
+
paused; enabling it again replaces the previous handler instead of stacking
|
|
288
|
+
a second one; the pre-warm URL is logged as scheme and host only.
|
|
289
|
+
- `scroll`, `click_element` and `fill_input` embed the CSS selector as a JSON
|
|
290
|
+
string literal; a selector with a backslash could break out of the
|
|
291
|
+
JavaScript string before.
|
|
292
|
+
- `--local` also blocks `Target.createTarget` sent through `send_cdp_command`
|
|
293
|
+
(only `Page.navigate` was checked).
|
|
294
|
+
- Cookie import refuses a `source_profile` that is not a single profile
|
|
295
|
+
directory name (`../..` could read outside the browser's user-data root).
|
|
296
|
+
- `get_network_logs` returns `Authorization`, `Proxy-Authorization`, `Cookie`
|
|
297
|
+
and `Set-Cookie` header values as `<redacted>` unless the new
|
|
298
|
+
`include_sensitive_headers: true` is passed.
|
|
299
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 raultov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|