feat(next-wxt): 支持 Options 在线编辑页面 MCP 脚本 - #536
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
[ci-test-pass] 未改动测试相关源码(next-sdk / webmcp-cli),已跳过单元测试与浏览器 E2E。
|
WalkthroughAdds user-defined MCP script storage, matching, editing, MAIN-world execution, built-in server override behavior, tab reinjection, Vitest coverage, and documentation updates for the Next WXT extension. ChangesUser MCP script lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Options
participant Background
participant Content
participant MainWorld
User->>Options: Edit and save MCP script
Options->>Background: Request reinjection
Background->>Content: Reload matching tab
Content->>Background: Request matching scripts
Background->>MainWorld: Execute user script through owned bridge
MainWorld-->>Background: Return execution result
Background-->>Content: Return skip-built-in decision
Content->>MainWorld: Inject built-in server when allowed
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (9)
packages/next-wxt/user-mcp-scripts/storage.ts (2)
75-75: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant expression.
input.id && store[input.id] ? input.id : input.id || newId()collapses toinput.id || newId()— both branches yieldinput.idwhen it is truthy.♻️ Simplify
- const id = input.id && store[input.id] ? input.id : input.id || newId() + const id = input.id || newId()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/user-mcp-scripts/storage.ts` at line 75, In the ID assignment expression, simplify the redundant conditional to use input.id when present and newId() otherwise; update the const id initialization without changing its fallback behavior.
87-95: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winWhole-store read-modify-write can lose concurrent updates.
Every mutator re-reads the full store and writes it back, so a rapid toggle while a save is in flight (or a save concurrent with an import) drops one of the two writes. Serializing writes behind a single promise chain in this module removes the window cheaply.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/user-mcp-scripts/storage.ts` around lines 87 - 95, Serialize all user MCP script store mutations in this module through a single promise chain, including the read-modify-write flow in removeUserMcpScript and the other mutators. Ensure each operation waits for the previous operation to finish before reading and persisting the store, while preserving the existing mutation results and API behavior.packages/next-wxt/entrypoints/options/Options.vue (1)
70-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
overflow: visibleapplies to every tab, not just the scripts tab.The comment scopes the intent to 页面 MCP 脚本, but this deep selector affects Skills / Token / 模型 content too. Worth a quick visual check on the other tabs, or scope it via a class on the tab content.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/entrypoints/options/Options.vue` around lines 70 - 73, Scope the overflow override in the .options-tabs styling to the MCP scripts tab content instead of applying it to every tab. Add or reuse a tab-specific class or selector in the Options component, and keep overflow visible only for that content while leaving Skills, Token, and model tabs unchanged.packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue (4)
620-622: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winGutter re-renders one node per line on every keystroke.
lineNumbersreallocates the array and Vue re-diffs onedivper line on each edit; a few-thousand-line script makes typing sluggish.v-for="n in sourceLineCount"drops the array allocation, and rendering the numbers as a single<pre>text block (orcounter-increment) removes the per-line nodes entirely.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue` around lines 620 - 622, Optimize the line-number gutter around lineNumbers by removing the per-line array allocation and individual div rendering. Use sourceLineCount directly with a single preformatted text block or CSS counters, preserving accurate numbering as the script changes.
102-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpread getter is unnecessary with a reactive source.
watch(editForm, ...)on areactiveobject is already deep; the() => ({ ...editForm })getter just allocates a copy on every tick.♻️ Simplify
-watch( - () => ({ ...editForm }), - () => { - if (isEditing.value) isDirty.value = true - }, - { deep: true } -) +watch(editForm, () => { + if (isEditing.value) isDirty.value = true +})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue` around lines 102 - 108, Update the watcher around editForm to watch the reactive object directly instead of using the spread getter, while preserving the existing deep-change callback that marks isDirty when isEditing is true.
211-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
openCreateForcedduplicatesopenCreate, and the discard path skipsclearValidate.Both paths build the same default meta and reset state; only the dirty-guard differs. Extracting the body would also fix the divergence:
openCreateForcedand the id branch ofconfirmDiscardnever calleditFormRef.clearValidate(), so stale validation errors from the abandoned draft can persist into the newly opened script.♻️ Extract shared draft initializer
+function applyNewDraft() { + selectedKey.value = '__new__' + fillFormFromMeta(createDefaultScriptMeta({ name: '我的页面工具', matches: ['*://example.com/*'] })) + collapseNames.value = ['meta'] + nextTick(() => { + isDirty.value = false + editFormRef.value?.clearValidate?.() + focusEditor() + }) +} + function openCreate() { - trySelect('__new__', () => { - selectedKey.value = '__new__' - const meta = createDefaultScriptMeta({ name: '我的页面工具', matches: ['*://example.com/*'] }) - fillFormFromMeta(meta) - collapseNames.value = ['meta'] - nextTick(() => { - isDirty.value = false - editFormRef.value?.clearValidate?.() - focusEditor() - }) - }) + trySelect('__new__', applyNewDraft) }(then use
applyNewDraft()in place ofopenCreateForced())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue` around lines 211 - 241, Extract the shared new-script draft initialization from openCreate into an applyNewDraft helper, including default metadata, form population, collapse state, dirty-state reset, editor focus, and editFormRef.clearValidate(). Use applyNewDraft() from both openCreate and openCreateForced, and ensure the existing-id branch of confirmDiscard also clears validation when loading the selected script.
623-631: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an accessible label to the code textarea.
The source editor has no
aria-labelor associated<label>, so screen readers announce it as an unlabeled text field.♿ Label the editor
<textarea ref="sourceEditorRef" v-model="editForm.source" class="source-editor" + aria-label="脚本源码(JavaScript)" spellcheck="false"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue` around lines 623 - 631, Add an accessible label to the textarea identified by sourceEditorRef in the source editor template, using an aria-label or an associated label element with descriptive text. Preserve the existing v-model and event bindings.packages/next-wxt/user-mcp-scripts/match.ts (1)
81-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused RegExp factory.
matchPatternToRegExpis not consumed by the rest of the codebase; keepmatchUrlas the single match implementation and delete the exported compiled-regex helper to avoid future drift.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/user-mcp-scripts/match.ts` around lines 81 - 89, Remove the exported matchPatternToRegExp function and its associated unused helper logic from match.ts. Keep matchUrl as the sole URL-pattern matching implementation, ensuring no remaining imports or references depend on the deleted RegExp factory.packages/next-wxt/components.d.ts (1)
12-25: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winDeclare the individual TinyVue component packages used by the auto-generated declarations.
These
components.d.tsentries matchTinyVueSingleResolver’s per-component package mapping, butnext-wxtonly declares@opentiny/vue, soTinyAlert,TinyCollapse,TinyCollapseItem,TinyTag, andTinyTooltipare phantom type-only dependencies. Add the same sub-packages as devDependencies to avoid broken type-checks when installing/upgrading.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/next-wxt/components.d.ts` around lines 12 - 25, Update the next-wxt package’s development dependencies to include the individual `@opentiny/vue` component packages referenced by the TinyAlert, TinyCollapse, TinyCollapseItem, TinyTag, and TinyTooltip declarations, matching TinyVueSingleResolver’s package mapping. Keep the existing generated declarations unchanged and add the corresponding sub-packages so type-checking resolves them directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue`:
- Around line 394-411: Update onSourceKeydown so Tab remains trapped only for
indentation when appropriate, while providing a keyboard escape path to move
focus out of the editor (such as an Esc-then-Tab flow), and document that
shortcut in the 提示 hint. Also normalize e.key with toLowerCase() for the
Ctrl/Cmd+S check so uppercase S is handled.
- Around line 356-363: Update the export flow around the anchor click in
UserMcpScriptsTab so the object URL remains valid until the download has
started. Defer URL.revokeObjectURL(url) instead of revoking it synchronously,
while preserving the existing generated filename and download behavior.
- Around line 284-305: Update the save flow around upsertUserMcpScript and
notifyReinject to capture the existing script’s matches before calling
upsertUserMcpScript, then pass that pre-save snapshot along with the new script
data so tabs matching old patterns are refreshed. Wrap the upsert await in error
handling and display thrown failures through Message.message instead of allowing
an unhandled rejection.
In `@packages/next-wxt/public/vendor/user-mcp-exec.js`:
- Around line 7-16: Replace the page-writable global execution bridge with an
extension-owned capability: in packages/next-wxt/public/vendor/user-mcp-exec.js
lines 7-16, never reuse a pre-existing window[KEY], establish immutable
ownership, and fail closed on collisions; in
packages/next-wxt/user-mcp-scripts/exec-bridge.ts lines 8-16, update the bridge
contract to use that capability rather than a predictable fixed global; in
packages/next-wxt/entrypoints/background/inject-user-mcp-scripts.ts lines 35-41,
pass script source only after verifying the extension-owned capability and
return failure when verification cannot be established.
In `@packages/next-wxt/specs/REQ-20260730-user-mcp-scripts/design.md`:
- Line 42: Update the dependency statement in the design specification to match
the implementation: remove jsdom from the listed next-wxt devDependencies unless
browser APIs are intentionally required. Keep Vitest and the test script
documented, consistent with vitest.config.ts using the node environment.
In `@packages/next-wxt/test/user-mcp-scripts/csp-bridge-repro.test.ts`:
- Around line 23-27: Extend the test around buildBridgeInvokeSnippet to exercise
executeUserSourceInMainWorld from inject-user-mcp-scripts.ts, mocking or driving
the executor as needed. Assert that execution uses world: 'MAIN' and invokes
window.__NEXT_WXT_EXEC_USER_MCP_SCRIPT__ with the user source, while retaining
the existing snippet string assertions as supplemental coverage.
In `@packages/next-wxt/user-mcp-scripts/match.ts`:
- Around line 113-122: Update validateMatchPattern to reject match patterns
containing a port, preventing MATCH_RE from accepting patterns that the matching
logic cannot compare correctly. Preserve existing hostname and wildcard
validation behavior for portless patterns, and ensure invalid port-bearing
patterns return the established validation error.
In `@packages/next-wxt/user-mcp-scripts/storage.ts`:
- Around line 97-102: Update setUserMcpScriptEnabled to modify only the existing
script’s enabled flag directly in the store instead of calling
upsertUserMcpScript, preserving updatedAt and sidebar ordering. Keep the
missing-script error response and UpsertResult contract unchanged.
- Around line 153-164: The import loop in the script import flow must force
every successfully normalized imported script to disabled regardless of its
source enabled value, including scripts with replacesBuiltIn set. Track entries
rejected by normalizeScript or validateMatchPatterns in a skipped count, and
include that count alongside imported in the returned result so the UI can
report partial failures.
In `@packages/next-wxt/user-mcp-scripts/template.ts`:
- Around line 47-53: Update the toolSlug generation near the name normalization
so non-ASCII-only names receive a unique short suffix instead of sharing the
static user_mcp_hello fallback. Preserve the existing normalization and length
limit, ensure the resulting slug remains valid for the registration guard, and
keep deterministic slugs for names that already produce non-empty ASCII output.
---
Nitpick comments:
In `@packages/next-wxt/components.d.ts`:
- Around line 12-25: Update the next-wxt package’s development dependencies to
include the individual `@opentiny/vue` component packages referenced by the
TinyAlert, TinyCollapse, TinyCollapseItem, TinyTag, and TinyTooltip
declarations, matching TinyVueSingleResolver’s package mapping. Keep the
existing generated declarations unchanged and add the corresponding sub-packages
so type-checking resolves them directly.
In `@packages/next-wxt/entrypoints/options/Options.vue`:
- Around line 70-73: Scope the overflow override in the .options-tabs styling to
the MCP scripts tab content instead of applying it to every tab. Add or reuse a
tab-specific class or selector in the Options component, and keep overflow
visible only for that content while leaving Skills, Token, and model tabs
unchanged.
In `@packages/next-wxt/entrypoints/options/UserMcpScriptsTab.vue`:
- Around line 620-622: Optimize the line-number gutter around lineNumbers by
removing the per-line array allocation and individual div rendering. Use
sourceLineCount directly with a single preformatted text block or CSS counters,
preserving accurate numbering as the script changes.
- Around line 102-108: Update the watcher around editForm to watch the reactive
object directly instead of using the spread getter, while preserving the
existing deep-change callback that marks isDirty when isEditing is true.
- Around line 211-241: Extract the shared new-script draft initialization from
openCreate into an applyNewDraft helper, including default metadata, form
population, collapse state, dirty-state reset, editor focus, and
editFormRef.clearValidate(). Use applyNewDraft() from both openCreate and
openCreateForced, and ensure the existing-id branch of confirmDiscard also
clears validation when loading the selected script.
- Around line 623-631: Add an accessible label to the textarea identified by
sourceEditorRef in the source editor template, using an aria-label or an
associated label element with descriptive text. Preserve the existing v-model
and event bindings.
In `@packages/next-wxt/user-mcp-scripts/match.ts`:
- Around line 81-89: Remove the exported matchPatternToRegExp function and its
associated unused helper logic from match.ts. Keep matchUrl as the sole
URL-pattern matching implementation, ensuring no remaining imports or references
depend on the deleted RegExp factory.
In `@packages/next-wxt/user-mcp-scripts/storage.ts`:
- Line 75: In the ID assignment expression, simplify the redundant conditional
to use input.id when present and newId() otherwise; update the const id
initialization without changing its fallback behavior.
- Around line 87-95: Serialize all user MCP script store mutations in this
module through a single promise chain, including the read-modify-write flow in
removeUserMcpScript and the other mutators. Ensure each operation waits for the
previous operation to finish before reading and persisting the store, while
preserving the existing mutation results and API behavior.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fffa1e11-9db4-4f21-8f95-842a422e982a
📒 Files selected for processing (26)
docs/ai-extension/next-wxt.mdpackages/next-wxt/components.d.tspackages/next-wxt/entrypoints/background.tspackages/next-wxt/entrypoints/background/inject-user-mcp-scripts.tspackages/next-wxt/entrypoints/content.tspackages/next-wxt/entrypoints/options/Options.vuepackages/next-wxt/entrypoints/options/UserMcpScriptsTab.vuepackages/next-wxt/mcp-servers/types.d.tspackages/next-wxt/package.jsonpackages/next-wxt/public/vendor/user-mcp-exec.jspackages/next-wxt/specs/REQ-20260730-user-mcp-scripts/design.mdpackages/next-wxt/specs/REQ-20260730-user-mcp-scripts/requirements.mdpackages/next-wxt/specs/REQ-20260730-user-mcp-scripts/tasks.mdpackages/next-wxt/test/user-mcp-scripts/csp-bridge-repro.test.tspackages/next-wxt/test/user-mcp-scripts/match.test.tspackages/next-wxt/test/user-mcp-scripts/resolve.test.tspackages/next-wxt/test/user-mcp-scripts/storage.test.tspackages/next-wxt/user-mcp-scripts/exec-bridge.tspackages/next-wxt/user-mcp-scripts/index.tspackages/next-wxt/user-mcp-scripts/match.tspackages/next-wxt/user-mcp-scripts/resolve.tspackages/next-wxt/user-mcp-scripts/storage.tspackages/next-wxt/user-mcp-scripts/template.tspackages/next-wxt/user-mcp-scripts/types.tspackages/next-wxt/vitest.config.tspackages/next-wxt/wxt.config.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/next-wxt/public/vendor/user-mcp-exec.js`:
- Around line 11-37: Replace the forgeable __NEXT_WXT_OWNED__ marker with an
extension-controlled, non-forgeable capability token shared by the bridge and
its verifier, and enforce the required injection/check ordering so page code
cannot supply an accepted function. Update install() in
packages/next-wxt/public/vendor/user-mcp-exec.js, the bridge ownership logic in
packages/next-wxt/user-mcp-scripts/exec-bridge.ts, and injection handling in
packages/next-wxt/entrypoints/background/inject-user-mcp-scripts.ts; document
and assert the ordering in wxt.config.ts and content.ts.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cece898d-6830-4eb1-9ce0-e6f99922c651
📒 Files selected for processing (9)
packages/next-wxt/entrypoints/background/inject-user-mcp-scripts.tspackages/next-wxt/entrypoints/options/Options.vuepackages/next-wxt/entrypoints/options/UserMcpScriptsTab.vuepackages/next-wxt/public/vendor/user-mcp-exec.jspackages/next-wxt/specs/REQ-20260730-user-mcp-scripts/design.mdpackages/next-wxt/user-mcp-scripts/exec-bridge.tspackages/next-wxt/user-mcp-scripts/match.tspackages/next-wxt/user-mcp-scripts/storage.tspackages/next-wxt/user-mcp-scripts/template.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/next-wxt/specs/REQ-20260730-user-mcp-scripts/design.md
… to enhance security and prevent spoofing
Pull Request (OpenTiny NEXT-SDKs)
Summary
What is the current behavior?
What is the new behavior?
Does this PR introduce a breaking change?
Other information
(可选。Issue 请用 GitHub 原生关联,无需手填路径。)
Summary by CodeRabbit