Skip to content

feat: add opt-in userscript invocation and structured result tools - #2

Merged
gera2ld merged 7 commits into
violentmonkey:mainfrom
ZZZZJJJ0928:codex/mcp-script-results
Nov 3, 2026
Merged

gera2ld merged 7 commits into
violentmonkey:mainfrom
ZZZZJJJ0928:codex/mcp-script-results

Conversation

@ZZZZJJJ0928

@ZZZZJJJ0928 ZZZZJJJ0928 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

An agent developing a userscript can install it, reload a granted tab and inspect GM_log, but testing the same parser, validator or calculation with several inputs currently requires editing the script or adding a custom trigger. Asynchronous log output also needs an ad hoc convention to associate each result with its request.

This change lets an agent discover a userscript's explicitly registered, named handlers, invoke one with JSON input and receive a structured value or error, without repeating the script's top-level initialization. Scripts can also deliberately report results, on their own or attached to a call.

Interfaces

The tool shapes follow the userscript API agreed in violentmonkey/violentmonkey#2726. The tools are documented in the README Tools table and in docs/contract.md, and the GM.mcp API reference lives with the extension docs.

  • script_handlers({ tabId }) returns scripts: [{ scriptId, frameId, handlers: [{ name, description?, inputSchema? }] }]. inputSchema is a hint provided by the script and is not validated.
  • script_call({ tabId, scriptId, handler, frameId?, params?, callId?, wait?, timeoutMs? }) returns a call record (kind: "call") with a correlated call ID and status.
  • script_results({ tabId, scriptId?, callId?, afterSeq?, limit? }) reads retained calls and reports (kind: "report", with the callId of their call, if any) with a sequence cursor.
  • The companion extension provides GM.mcp / GM_mcp = { register, report }. Handlers receive { params, report }, where report is bound to the call, so userscripts never handle call IDs.

This PR adds the protocol schemas, inferred types, server transport/capability tests, README and contract documentation, and a changeset. Tools are exposed only when the connected extension advertises support; existing extensions keep their current tool list. Read-only mode exposes discovery and results, while excluding script_call.

Scope and authorization

This extends the development contract, which excluded driving userscripts from outside; the Non-goals entry is updated accordingly. There is still no generic evaluation, DOM query, navigation, console capture or network capture tool. The agent can only call handlers a userscript explicitly registers, and results contain only what the script returns or reports.

Invocation requires a connected MCP session, an explicitly granted tab, an enabled installed script and the named handler registered in that document. Scripts opt in with @grant GM.mcp (or GM_mcp) and an explicit registration. Registration is an application-level opt-in, not a new security sandbox: an agent already allowed to edit scripts can add a handler, and a handler can do anything its userscript can.

File saving, download directories, local filesystem access and application-specific integrations are deferred to a separate change. Existing GM_download behavior is unchanged.

Lifecycle and limits

  • Script errors are returned in a valid call record; a successful MCP request does not imply a successful script. Protocol errors cover invalid arguments (400), unknown handlers (404) and denied access (403).
  • A timeout ends the wait, not the script or its side effects. A late return value can still settle a retained timed_out call.
  • Reusing a retained call ID with the same script, frame, handler and params returns the existing record without running the handler again; different arguments fail. Eviction, disconnect or restart ends this guarantee, so there is no durable exactly-once execution, and clients should not automatically retry side effects with a fresh ID.
  • The extension caps serialized input and output at 256k characters each, errors at 8,192 characters, pending calls at 32 per tab, and retention at 100 entries and about 2 MB per tab. Settled calls and reports expire after five minutes; reporting keeps a call retained.
  • Navigation or reload cancels unfinished calls for the replaced document. Back/forward cache restoration renews the channel and restores handlers with their metadata without rerunning script initialization, while old calls stay cancelled. Revoking the grant, closing the tab or disconnecting clears channels and results.
  • The server's default 30-second transport timeout leaves room for the maximum 25-second execution timeout.

Compatibility and release

The MCP repository owns the tool schemas, typed client interfaces, capability negotiation and transport tests. The extension PR owns the GM API, authorization, per-document dispatch, result retention and page lifecycle. The README leaves the required Violentmonkey version empty until the extension release is known. The MCP package release and extension rollout should be coordinated.

Validation

  • MCP automated suite: 28 tests passed, plus typecheck and build. Cases cover the handler argument, handler-name validation and malformed script_handlers/script_results output.
  • Companion extension: 172 tests passed, plus lint and MV2/MV3 builds.
  • Paired real-browser regression on 2026-11-03: 46/46 cases passed in Google Chrome 156.0.8078.80, MV3, macOS arm64, using a headed browser, an isolated profile and the per-extension "Allow User Scripts" switch.
  • Calls used the real MCP SDK client over Streamable HTTP → server → WebSocket → extension → installed userscripts. The extension backend was not mocked.
  • GM_mcp ran with @inject-into page and GM.mcp with @inject-into content, each in the top document, a same-origin iframe and a cross-origin iframe, with 12 named handlers per frame.
Area Verified behavior
Registration Named handlers with descriptions and input schemas in both realms and all frame contexts; unregister() removes only its own handlers
Calls and results Synchronous JSON and falsy values, asynchronous returns, concurrent correlation, call-bound reports before and after settling, unsolicited reports and exclusive result cursors
Failures and limits Thrown errors, rejected promises, circular values, BigInt, oversized inputs and outputs, unknown or missing handler names, missing frame handlers and disabled scripts
Call lifecycle Retained call-ID deduplication, conflicting reuse with other params or handler, wait: false, execution timeout and late completion
Page restoration Real back/forward cache restoration with trusted pageshow.persisted === true, renewed document channels, handlers and metadata restored without repeated initialization
Authorization and cleanup Revocation and regrant, rejection of cached old authorization sessions, reload, disconnect/reconnect, read-only mode and tab closure

Late replies could not overwrite cancelled calls, and no unexpected page JavaScript errors were observed. Firefox has not yet been verified in a real browser; build success is not a cross-browser compatibility claim.

Browser-tested revisions: MCP 162a229 and extension 1cdae382; later commits address review nits (a redundant assignment, formatting, message wording) and documentation.

Add capability-negotiated handler discovery, JSON invocation and result polling. Cover structured responses, input validation, authorization errors and read-only filtering with transport tests.
ZZZZJJJ0928 and others added 2 commits November 3, 2026 13:21
Follow the API agreed in violentmonkey/violentmonkey#2726:

- script_handlers lists named handlers per script and frame, with an
  optional description and an inputSchema hint that is not validated.
- script_call requires a handler name.
- script_results returns calls and reports as a discriminated union on
  `kind`; reports carry the callId of their call, if any.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@gera2ld gera2ld left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. The design looks good. I have a few doc changes to request:

  1. Delete docs/script-execution.md. Move the proposal and validation content into this PR description, and keep the GM.mcp API reference with the extension docs. If the server needs anything beyond the README, add a short tools section to docs/contract.md and link to the extension docs.
  2. In the README, remove the "proposed" paragraph. Add the three tools to the Tools table with annotations (script_call is destructive and open-world) and leave the required Violentmonkey version empty for now.
  3. Update the Non-goals entry in docs/contract.md about not driving userscripts from outside.

As requested in review:

- Remove docs/script-execution.md; its proposal and validation notes
  move to the PR description, and the GM.mcp API reference stays with
  the extension docs.
- List script_handlers, script_call and script_results in the README
  Tools table, with an empty Violentmonkey version for now.
- Add the three tools to docs/contract.md and update the Non-goals
  entry about driving userscripts from outside.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ZZZZJJJ0928

Copy link
Copy Markdown
Contributor Author

@gera2ld Thanks! All three are done in a735d6f:

  1. docs/script-execution.md is removed. The proposal and validation notes are now in this PR description, and the GM.mcp API reference stays in the extension docs. docs/contract.md gained short script_handlers, script_call and script_results sections (schemas, deduplication, timeouts, lifecycle, caps, error codes) that link to the extension's GM.mcp reference. That link goes to docs/mcp-script-results.md on violentmonkey's master, so it resolves once feat: add MCP userscript calls, results and BFCache restoration violentmonkey#2726 is merged.
  2. The README drops the "proposed" paragraph. The three tools are in the Tools table (script_handlers and script_results read-only, script_call destructive and open-world) with the Violentmonkey version left empty. I also added them to the list of tab tools that need a grant, and script_call to the tools excluded by --readonly.
  3. The Non-goals entry now says the agent still can't run arbitrary code in a page or query it, and can only call handlers a userscript explicitly registers with GM.mcp. The "Observation boundary", grant list and Errors table were updated to match.

@ZZZZJJJ0928
ZZZZJJJ0928 requested a review from gera2ld November 3, 2026 08:30

@gera2ld gera2ld left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@gera2ld
gera2ld merged commit 9eddf33 into violentmonkey:main Nov 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants