Conversation
Expose navigator.modelContext.registerTool definitions for decode state, example fragments, artifact selection, copy/download/print, and clearing the hash. Use a ref updated each frame so execute callbacks stay fresh, and AbortController to unregister on unmount. Add minimal TS typings and unit tests with a mocked ModelContext. Co-authored-by: Aanish Bhirud <baanish@users.noreply.github.com>
📝 WalkthroughWalkthroughThis PR introduces WebMCP tool registration infrastructure to integrate agent-driven tool execution into the viewer. It includes TypeScript type declarations for the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| requestUserInteraction(callback: () => Promise<unknown>): Promise<unknown>; | ||
| } | ||
|
|
||
| type ToolExecuteCallback = (input: object, client: ModelContextClient) => Promise<unknown>; |
There was a problem hiding this comment.
WARNING: Type signature mismatch - ToolExecuteCallback requires two parameters (input, client), but all tool execute implementations only accept (input) or (). This violates the declared type and would fail strict type checking. Make client optional: (input: object, client?: ModelContextClient) => Promise<unknown>.
|
|
||
| /** Maps example keys to sample link hashes (same order as `WEBMCP_EXAMPLE_KEYS`). */ | ||
| export function buildExampleHashByKey(sampleHashes: readonly string[]): Record<WebMcpExampleKey, string> { | ||
| const out = {} as Record<WebMcpExampleKey, string>; |
There was a problem hiding this comment.
WARNING: Return type incorrect - buildExampleHashByKey returns Record<WebMcpExampleKey, string> (all keys required) but only assigns keys conditionally when h !== undefined. If sampleHashes is shorter than WEBMCP_EXAMPLE_KEYS, the returned object is missing keys, violating the type contract. Change return type to Partial<Record<WebMcpExampleKey, string>> or ensure assignment always happens.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Other Observations (not in diff)No issues found outside the diff. Files Reviewed (5 files)
Reviewed by step-3.5-flash · 369,860 tokens |
Deploying agent-render with
|
| Latest commit: |
970a863
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b6ce9d32.agent-render.pages.dev |
| Branch Preview URL: | https://cursor-webmcp-register-tools.agent-render.pages.dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 970a86318d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| read().selectArtifact(id); | ||
| return { ok: true, artifactId: id }; |
There was a problem hiding this comment.
Validate artifact ID before claiming selection succeeded
agent_render.select_artifact always returns { ok: true } for any non-empty artifactId, but it never verifies that the ID exists in the current bundle before calling selectArtifact. With a typo or stale ID, the viewer path normalizes activeArtifactId to the first artifact (via envelope normalization) rather than selecting the requested artifact, so the tool reports success while navigating to an unintended artifact. This can mislead agent workflows that rely on the response to confirm state changes.
Useful? React with 👍 / 👎.
| execute: async () => { | ||
| await read().copyActiveArtifact(); | ||
| return { ok: true }; | ||
| }, |
There was a problem hiding this comment.
🟡 copy_active_artifact tool always returns { ok: true } even when clipboard copy fails
The execute handler for agent_render.copy_active_artifact unconditionally returns { ok: true } after awaiting copyActiveArtifact(). However, the underlying handleArtifactCopy at src/components/viewer-shell.tsx:397-419 catches clipboard errors internally (setting UI state to "failed") without re-throwing them. It also silently returns when there is no active artifact. This means the tool reports success to the agent caller in all cases—including when the clipboard write actually failed or when no artifact was loaded—giving incorrect feedback that could mislead an agent into thinking the content was copied when it was not.
Prompt for agents
The copy_active_artifact tool always returns { ok: true } because handleArtifactCopy (viewer-shell.tsx:397-419) catches clipboard errors internally and never propagates them. Two failure modes are silently swallowed: (1) no active artifact loaded, and (2) copyTextToClipboard throws a clipboard permission/API error.
To fix this, the tool needs visibility into the copy outcome. One approach: refactor handleArtifactCopy (or add a variant for the WebMCP path) so it returns or throws on failure, letting the tool execute handler return { ok: false, error: 'no_active_artifact' } or { ok: false, error: 'clipboard_failed' } as appropriate. Alternatively, the AgentRenderWebMcpActions.copyActiveArtifact contract could be changed to return a boolean or a result object instead of Promise<void>.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/lib/webmcp/register-agent-render-tools.ts (1)
267-277: Avoid returning a partial object as a completeRecord.
buildExampleHashByKey()skips missing hashes, but the return type promises everyWebMcpExampleKeyis present. ReturnPartial<Record<...>>or fail fast when a key has no matching hash.♻️ Proposed type-safe return
/** Maps example keys to sample link hashes (same order as `WEBMCP_EXAMPLE_KEYS`). */ -export function buildExampleHashByKey(sampleHashes: readonly string[]): Record<WebMcpExampleKey, string> { - const out = {} as Record<WebMcpExampleKey, string>; +export function buildExampleHashByKey(sampleHashes: readonly string[]): Partial<Record<WebMcpExampleKey, string>> { + const out: Partial<Record<WebMcpExampleKey, string>> = {}; for (let i = 0; i < WEBMCP_EXAMPLE_KEYS.length; i += 1) { const key = WEBMCP_EXAMPLE_KEYS[i]; const h = sampleHashes[i];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/webmcp/register-agent-render-tools.ts` around lines 267 - 277, The function buildExampleHashByKey currently returns a complete Record<WebMcpExampleKey, string> while skipping undefined sampleHashes; change its signature to return Partial<Record<WebMcpExampleKey, string>> OR validate/throw when any WEBMCP_EXAMPLE_KEYS entry has no corresponding sampleHashes[i]; locate buildExampleHashByKey and the loop using WEBMCP_EXAMPLE_KEYS and either adjust the return type to Partial<Record<WebMcpExampleKey,string>> and keep current behavior, or add a check inside the loop (or after) to throw a descriptive error if any h is undefined so the function can safely continue to return Record<WebMcpExampleKey,string>.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/webmcp/register-agent-render-tools.ts`:
- Around line 176-183: The execute handler currently calls
read().selectArtifact(id) and returns { ok: true } without verifying the
artifactId exists; before mutating, check that the given artifactId is present
in the viewer's decoded envelope or artifact list (e.g. via a non-mutating
lookup such as read().getArtifactById(id) or checking
read().decodedEnvelope.artifacts/includes(id)), and if it is missing return {
ok: false, error: "unknown_artifactId" } instead of calling
read().selectArtifact; only call read().selectArtifact(id) and return success
when that lookup confirms the id exists.
- Around line 188-240: The three registered tools use an incorrect execute
signature and need to accept (input: object, client: ModelContextClient); update
each execute to async (input, client) => { await
client.requestUserInteraction(() => read().copyActiveArtifact()) ; return { ok:
true } } (and similarly for read().downloadActiveArtifact and
read().printActiveMarkdown) so the clipboard, download and print calls run
inside client.requestUserInteraction() and are properly user-activation gated
while preserving the existing return shape.
In `@tests/webmcp/register-agent-render-tools.test.ts`:
- Around line 8-18: The afterEach hook currently restores navigator.modelContext
and calls vi.restoreAllMocks(), but it does not unset globals stubbed with
vi.stubGlobal (e.g., isSecureContext), so add a call to vi.unstubAllGlobals()
inside the afterEach block (alongside the existing Object.defineProperty restore
and vi.restoreAllMocks()) to ensure any globals stubbed in tests are fully
unstubbed and do not leak into subsequent tests.
---
Nitpick comments:
In `@src/lib/webmcp/register-agent-render-tools.ts`:
- Around line 267-277: The function buildExampleHashByKey currently returns a
complete Record<WebMcpExampleKey, string> while skipping undefined sampleHashes;
change its signature to return Partial<Record<WebMcpExampleKey, string>> OR
validate/throw when any WEBMCP_EXAMPLE_KEYS entry has no corresponding
sampleHashes[i]; locate buildExampleHashByKey and the loop using
WEBMCP_EXAMPLE_KEYS and either adjust the return type to
Partial<Record<WebMcpExampleKey,string>> and keep current behavior, or add a
check inside the loop (or after) to throw a descriptive error if any h is
undefined so the function can safely continue to return
Record<WebMcpExampleKey,string>.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4b3a4870-c852-492c-b9c4-df537597bc77
📒 Files selected for processing (5)
src/components/viewer-shell.tsxsrc/lib/webmcp/register-agent-render-tools.tssrc/types/webmcp.d.tstests/webmcp/register-agent-render-tools.test.tstsconfig.json
| execute: async (input) => { | ||
| const id = (input as { artifactId?: string }).artifactId; | ||
| if (!id) { | ||
| return { ok: false, error: "missing_artifactId" }; | ||
| } | ||
| read().selectArtifact(id); | ||
| return { ok: true, artifactId: id }; | ||
| }, |
There was a problem hiding this comment.
Validate artifactId before mutating the fragment.
This tool accepts arbitrary agent input but returns { ok: true } even when the ID is not in the decoded envelope. With the current viewer handler, that can rewrite the URL with an invalid activeArtifactId.
🛡️ Proposed validation
execute: async (input) => {
const id = (input as { artifactId?: string }).artifactId;
if (!id) {
return { ok: false, error: "missing_artifactId" };
}
- read().selectArtifact(id);
+ const actions = read();
+ const state = actions.getViewerState();
+ if (!state.decodeOk) {
+ return { ok: false, error: "no_decoded_envelope" };
+ }
+ if (!state.artifactIds.includes(id)) {
+ return { ok: false, error: "unknown_artifact_id" };
+ }
+ actions.selectArtifact(id);
return { ok: true, artifactId: id };
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| execute: async (input) => { | |
| const id = (input as { artifactId?: string }).artifactId; | |
| if (!id) { | |
| return { ok: false, error: "missing_artifactId" }; | |
| } | |
| read().selectArtifact(id); | |
| return { ok: true, artifactId: id }; | |
| }, | |
| execute: async (input) => { | |
| const id = (input as { artifactId?: string }).artifactId; | |
| if (!id) { | |
| return { ok: false, error: "missing_artifactId" }; | |
| } | |
| const actions = read(); | |
| const state = actions.getViewerState(); | |
| if (!state.decodeOk) { | |
| return { ok: false, error: "no_decoded_envelope" }; | |
| } | |
| if (!state.artifactIds.includes(id)) { | |
| return { ok: false, error: "unknown_artifact_id" }; | |
| } | |
| actions.selectArtifact(id); | |
| return { ok: true, artifactId: id }; | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/webmcp/register-agent-render-tools.ts` around lines 176 - 183, The
execute handler currently calls read().selectArtifact(id) and returns { ok: true
} without verifying the artifactId exists; before mutating, check that the given
artifactId is present in the viewer's decoded envelope or artifact list (e.g.
via a non-mutating lookup such as read().getArtifactById(id) or checking
read().decodedEnvelope.artifacts/includes(id)), and if it is missing return {
ok: false, error: "unknown_artifactId" } instead of calling
read().selectArtifact; only call read().selectArtifact(id) and return success
when that lookup confirms the id exists.
| modelContext.registerTool( | ||
| { | ||
| name: "agent_render.copy_active_artifact", | ||
| title: "Copy active artifact", | ||
| description: "Copies the current artifact body (text) to the clipboard, same as the Copy button.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {}, | ||
| additionalProperties: false, | ||
| }, | ||
| execute: async () => { | ||
| await read().copyActiveArtifact(); | ||
| return { ok: true }; | ||
| }, | ||
| }, | ||
| { signal }, | ||
| ); | ||
|
|
||
| modelContext.registerTool( | ||
| { | ||
| name: "agent_render.download_active_artifact", | ||
| title: "Download active artifact", | ||
| description: "Downloads the active artifact as a file, same as the Download button.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {}, | ||
| additionalProperties: false, | ||
| }, | ||
| execute: async () => { | ||
| read().downloadActiveArtifact(); | ||
| return { ok: true }; | ||
| }, | ||
| }, | ||
| { signal }, | ||
| ); | ||
|
|
||
| modelContext.registerTool( | ||
| { | ||
| name: "agent_render.print_markdown_artifact", | ||
| title: "Print markdown", | ||
| description: | ||
| "If the active artifact is markdown, opens the browser print dialog for print-to-PDF. No-op for other kinds.", | ||
| inputSchema: { | ||
| type: "object", | ||
| properties: {}, | ||
| additionalProperties: false, | ||
| }, | ||
| execute: async () => { | ||
| read().printActiveMarkdown(); | ||
| return { ok: true }; | ||
| }, | ||
| }, | ||
| { signal }, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WebMCP navigator.modelContext registerTool requestUserInteraction clipboard download print user activation
💡 Result:
WebMCP (Web Model Context Protocol) is a W3C draft browser API that extends navigator with navigator.modelContext, allowing websites to register tools callable by AI agents. Key methods include navigator.modelContext.registerTool to register tools with name, description, inputSchema, and execute callback. Tools receive input and a ModelContextClient during execution. requestUserInteraction is a method on ModelContextClient: client.requestUserInteraction(callback) pauses tool execution to request user input via an async callback (e.g., confirm dialog), resolving with the result for human-in-the-loop flows like confirmations before destructive actions. clipboard, download, and print relate to standard Navigator APIs (navigator.clipboard for Clipboard API; downloads/print via a[download] or window.print) and navigator.userActivation (UserActivation state for checking user gestures, e.g., navigator.userActivation.isActive). These are separate from WebMCP but relevant for tools needing permissions: user activation gates many browser features (autoplay, clipboard writes), so WebMCP tools performing such actions must check/await user activation, often via requestUserInteraction. No direct integration found between WebMCP and these specific APIs; tools can use them internally if activation allows. Spec is evolving (Chrome 146+ preview), requires secure contexts (HTTPS).
Citations:
- 1: https://webmachinelearning.github.io/webmcp
- 2: https://mcpcat.io/guides/build-webmcp-confirmation-flow-requestuserinteraction
- 3: https://www.webfuse.com/webmcp-cheat-sheet
- 4: https://mcpcat.io/guides/register-first-webmcp-tool
- 5: https://developer.mozilla.org/en-US/docs/Web/API/Navigator
- 6: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userActivation
🏁 Script executed:
# First, check the file structure and current implementation
head -20 src/lib/webmcp/register-agent-render-tools.tsRepository: baanish/agent-render
Length of output: 611
🏁 Script executed:
# Check if requestUserInteraction is used elsewhere in the codebase
rg "requestUserInteraction" --type ts --type tsx -B 2 -A 2Repository: baanish/agent-render
Length of output: 91
🏁 Script executed:
# Search for other tool execute callbacks to see the current pattern
rg "execute:\s*async" src/lib/webmcp/ --type ts --type tsx -A 3Repository: baanish/agent-render
Length of output: 91
🏁 Script executed:
# Check the actual implementation of the methods being called
rg "copyActiveArtifact|downloadActiveArtifact|printActiveMarkdown" --type ts --type tsx -B 2 -A 5Repository: baanish/agent-render
Length of output: 91
🏁 Script executed:
# Look for any WebMCP-related imports or documentation
rg "registerTool|modelContext" src/lib/webmcp/ --type ts --type tsx -B 1 -A 1 | head -40Repository: baanish/agent-render
Length of output: 91
🏁 Script executed:
# Find TypeScript files in the webmcp directory
fd "\.ts$" src/lib/webmcp/Repository: baanish/agent-render
Length of output: 109
🏁 Script executed:
# Search for requestUserInteraction usage
rg "requestUserInteraction" -A 3 -B 2Repository: baanish/agent-render
Length of output: 400
🏁 Script executed:
# Read the actual file to see the tool registrations
wc -l src/lib/webmcp/register-agent-render-tools.tsRepository: baanish/agent-render
Length of output: 113
🏁 Script executed:
# Get the specific lines mentioned in the review (188-240)
sed -n '180,245p' src/lib/webmcp/register-agent-render-tools.tsRepository: baanish/agent-render
Length of output: 1727
🏁 Script executed:
# Search for other tool registrations to see the callback pattern
rg "execute:" src/lib/webmcp/register-agent-render-tools.ts -A 4 -B 2Repository: baanish/agent-render
Length of output: 1691
🏁 Script executed:
# Search for the implementation of copyActiveArtifact
rg "copyActiveArtifact" -B 3 -A 10Repository: baanish/agent-render
Length of output: 4259
🏁 Script executed:
# Search for downloadActiveArtifact implementation
rg "downloadActiveArtifact" -B 3 -A 10Repository: baanish/agent-render
Length of output: 4282
🏁 Script executed:
# Search for printActiveMarkdown implementation
rg "printActiveMarkdown" -B 3 -A 10Repository: baanish/agent-render
Length of output: 4191
🏁 Script executed:
# Check the file structure to find where read() comes from
head -50 src/lib/webmcp/register-agent-render-tools.tsRepository: baanish/agent-render
Length of output: 1673
🏁 Script executed:
# Find and read the handler implementations
rg "handleArtifactCopy|handleArtifactDownload|handleMarkdownPrint" src/components/viewer-shell.tsx -B 2 -A 15Repository: baanish/agent-render
Length of output: 4359
🏁 Script executed:
# Find the copyTextToClipboard implementation to confirm it uses Clipboard API
rg "copyTextToClipboard" -B 2 -A 8Repository: baanish/agent-render
Length of output: 3940
Update tool callbacks to accept the client parameter and wrap user-interaction operations with requestUserInteraction.
The execute callbacks have an incorrect signature. Per the WebMCP spec (defined in src/types/webmcp.d.ts), callbacks must accept (input: object, client: ModelContextClient). The three operations—clipboard write, download, and print—use browser APIs that require user activation and will fail silently without proper gating. Wrap them with client.requestUserInteraction() as the host provides the activation gate.
Required changes
- execute: async () => {
- await read().copyActiveArtifact();
+ execute: async (_input, client) => {
+ await client.requestUserInteraction(async () => {
+ await read().copyActiveArtifact();
+ });
return { ok: true };
},
- execute: async () => {
- read().downloadActiveArtifact();
+ execute: async (_input, client) => {
+ await client.requestUserInteraction(async () => {
+ read().downloadActiveArtifact();
+ });
return { ok: true };
},
- execute: async () => {
- read().printActiveMarkdown();
+ execute: async (_input, client) => {
+ await client.requestUserInteraction(async () => {
+ read().printActiveMarkdown();
+ });
return { ok: true };
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/webmcp/register-agent-render-tools.ts` around lines 188 - 240, The
three registered tools use an incorrect execute signature and need to accept
(input: object, client: ModelContextClient); update each execute to async
(input, client) => { await client.requestUserInteraction(() =>
read().copyActiveArtifact()) ; return { ok: true } } (and similarly for
read().downloadActiveArtifact and read().printActiveMarkdown) so the clipboard,
download and print calls run inside client.requestUserInteraction() and are
properly user-activation gated while preserving the existing return shape.
| afterEach(() => { | ||
| Object.defineProperty(navigator, "modelContext", { | ||
| configurable: true, | ||
| value: originalModelContext, | ||
| }); | ||
| vi.restoreAllMocks(); | ||
| }); | ||
|
|
||
| it("registers tools when modelContext.registerTool exists", () => { | ||
| vi.stubGlobal("isSecureContext", true); | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether Vitest globals stubbed with vi.stubGlobal are automatically restored.
# Expect: either test config has unstubGlobals: true, or tests call vi.unstubAllGlobals().
rg -n -C3 'unstubGlobals|stubGlobal\(|unstubAllGlobals|restoreAllMocks' --glob '!node_modules'Repository: baanish/agent-render
Length of output: 1773
🏁 Script executed:
fd -name 'vitest.config.*' -o -name 'vite.config.*'Repository: baanish/agent-render
Length of output: 294
🏁 Script executed:
cat -n tests/webmcp/register-agent-render-tools.test.ts | head -40Repository: baanish/agent-render
Length of output: 1691
🏁 Script executed:
fd 'vitest.config' -o -type f -name 'vite.config*'Repository: baanish/agent-render
Length of output: 180
🏁 Script executed:
find . -maxdepth 2 -name 'vitest.config.*' -o -name 'vite.config.*' 2>/dev/nullRepository: baanish/agent-render
Length of output: 82
🏁 Script executed:
cat vitest.config.tsRepository: baanish/agent-render
Length of output: 504
🏁 Script executed:
rg 'unstubAllGlobals|unstubGlobals' --glob '!node_modules'Repository: baanish/agent-render
Length of output: 46
🏁 Script executed:
rg '^\s*it\(' tests/webmcp/register-agent-render-tools.test.tsRepository: baanish/agent-render
Length of output: 189
🏁 Script executed:
cat -n tests/webmcp/register-agent-render-tools.test.ts | sed -n '40,80p'Repository: baanish/agent-render
Length of output: 1157
Add vi.unstubAllGlobals() to the afterEach hook.
The isSecureContext stub from the first test will leak into the second test. Vitest does not automatically restore stubbed globals, so without explicit unstubbing, subsequent tests inherit previous stub state and experience unintended side effects.
🧪 Test isolation fix
afterEach(() => {
Object.defineProperty(navigator, "modelContext", {
configurable: true,
value: originalModelContext,
});
vi.restoreAllMocks();
+ vi.unstubAllGlobals();
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| afterEach(() => { | |
| Object.defineProperty(navigator, "modelContext", { | |
| configurable: true, | |
| value: originalModelContext, | |
| }); | |
| vi.restoreAllMocks(); | |
| }); | |
| it("registers tools when modelContext.registerTool exists", () => { | |
| vi.stubGlobal("isSecureContext", true); | |
| afterEach(() => { | |
| Object.defineProperty(navigator, "modelContext", { | |
| configurable: true, | |
| value: originalModelContext, | |
| }); | |
| vi.restoreAllMocks(); | |
| vi.unstubAllGlobals(); | |
| }); | |
| it("registers tools when modelContext.registerTool exists", () => { | |
| vi.stubGlobal("isSecureContext", true); | |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/webmcp/register-agent-render-tools.test.ts` around lines 8 - 18, The
afterEach hook currently restores navigator.modelContext and calls
vi.restoreAllMocks(), but it does not unset globals stubbed with vi.stubGlobal
(e.g., isSecureContext), so add a call to vi.unstubAllGlobals() inside the
afterEach block (alongside the existing Object.defineProperty restore and
vi.restoreAllMocks()) to ensure any globals stubbed in tests are fully unstubbed
and do not leak into subsequent tests.
|
Closing after triage: the WebMCP surface is speculative and expands product scope beyond static artifact viewing. |
Summary
Implements the WebMCP imperative API by calling
navigator.modelContext.registerTool()on page load (insideViewerShell). Tools expose the same actions users can take in the UI: read decode/fragment state, load built-in example fragments, switch artifacts in a bundle, copy/download/print, and clear the hash.Details
AbortControllerso all tools unregister on unmount, matching the agent skill guidance.useLayoutEffectsoexecutecallbacks always call the latest handlers without duplicate registration.src/types/webmcp.d.tsfor TypeScript andtsconfiginclusion forsrc/types/**/*.d.ts.navigator.modelContextto verify eight tools register.The spec uses
registerTool, notprovideContext—the implementation follows the current draft and Chrome/WebMCP skill.Validation
npm run check(lint, tests, typecheck, build)Summary by CodeRabbit