Skip to content

feat: eagerly expose in-page tools to browser agents - #376

Merged
antfu merged 4 commits into
mainfrom
eager-client-script
Sep 16, 2026
Merged

antfu merged 4 commits into
mainfrom
eager-client-script

Conversation

@posva

@posva posva commented Sep 9, 2026

Copy link
Copy Markdown
Contributor
  • allow in-page-script to execute without their panel to be active so they can register mcp tools
  • added automatic generation of mcp tools when agents is added to in-page functions

@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
devframe Skipped Skipped Sep 16, 2026 2:07am UTC

@posva
posva force-pushed the eager-client-script branch from 61555b9 to 7c2ad90 Compare September 9, 2026 15:56
Base automatically changed from fix/in-page-shared-state-codec to main September 9, 2026 23:20
@posva
posva force-pushed the eager-client-script branch from 72abe22 to efa762e Compare September 10, 2026 08:31
posva added a commit to posva/medula that referenced this pull request Sep 14, 2026
- exposeState registry, in-page channel with agent tools (list/get/set/patch)
- node devframe definition with help tool, plain HTML config page, connect.js
- Vite plugin, Next handler, Nuxt module
- local devframe build (PR devframes/devframe#376) vendored as tarballs
Rebase onto the 0.10 main and drop this branch's own eager client-script
implementation, which now lives in main via #387. Keep the browser-to-node
agent bridge that turns in-page channel functions into MCP tools.

An in-page channel function may now carry `agent` metadata (requiring
`jsonSerializable: true`). Page-script and panel endpoints register those as
browser-agent tools, mirror the manifest to node over an RPC bridge, and a
node-side tool provider registers them on the agent host so they surface over
MCP. Invocations round-trip back into the browser through
`devframe:agent:invoke-client-tool`.

Adapt to the 0.10 MCP move: the discovery-metadata improvement now lands in
`@devframes/agentic`'s connect surface, the browser-safe JSON-schema and
positional-arg helpers replace the pre-move paths, and the new diagnostic is
renumbered to DF0080 to avoid the DF0078 agentic collision.

Co-authored-by: agent <agent@opencode>
…dupe safety

Align the in-page `agent` option with #379: setting `agent` now implies
`jsonSerializable: true` instead of requiring it, and DF0080 only fires on an
explicit `jsonSerializable: false`.

Collapse the three copies of the agent safety inference (WebMCP, the MCP host,
and the in-page bridge) into one shared `resolveAgentSafety` helper, and drop
the redundant id de-duplication in the browser-agent registry since the
node-side tool provider already dedupes.

Co-authored-by: agent <agent@opencode>
Groundwork for #394. Each browser tab mints a stable client id
(`sessionStorage`-backed nanoid that survives reloads and RPC reconnects) and
tags its `devframe:agent:sync-client-tools` payload with it, so the node-side
client-agent registry records which tab each session belongs to.

This is the identifier only: no per-tab listing or routing yet. Tab
duplication copies `sessionStorage`, so duplicated tabs can briefly share an id
until the wider tab-metadata work adds disambiguation.

Co-authored-by: agent <agent@opencode>
The client-tool bridge only matters when the node exposes an MCP endpoint, so
gate it on `connectionMeta.mcp` (already forwarded in `__connection.json`) and
pull it in through a dynamic `import()`. Its code — the bridge, the browser
tool registry, and the client-id helper — now lands in its own chunk that a
non-MCP connection never downloads or runs.

Co-authored-by: agent <agent@opencode>
@antfu
antfu marked this pull request as ready for review September 16, 2026 02:48
Copilot AI lite review requested due to automatic review settings September 16, 2026 02:48
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 16, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +3 new · 🟠 ~7 changed · 🔴 -0 removed · 2 flows · 24 files · commit 38fd436


Architecture

Architecture diagram for devframes/devframe at 38fd436

10 components touched across 3 lanes.

Open the interactive canvas


Inside the changed components — 2 views

Component view — Browser Client Bridge

In-page channel agent registration, browser tool registry, and conditional RPC bridge.

Architecture view of Component view — Browser Client Bridge in devframes/devframe

Component view — Devframe Agent Host & MCP

Client agent session tracking, agent tool provider aggregation, and MCP gateway integration.

Architecture view of Component view — Devframe Agent Host & MCP in devframes/devframe

Data flow

Data flow diagram for devframes/devframe at 38fd436

Browser tool registration and sync · Agent invokes browser tool via MCP

Open the interactive canvas


The other flows — 1 sequence

Agent invokes browser tool via MCP

Sequence diagram of Agent invokes browser tool via MCP in devframes/devframe

View

  • Architecture lens
  • Data flow lens
  • Expand every detail

Tip

Untick Architecture lens or Data flow lens under View to hide a diagram, or tick Expand every detail to open every section. The comment redraws in a few seconds.

🪧 More tips
  • Run npx skills add coldteadotai/pr-lens, then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Run npx @coldtea/pr-lens-cli analyze --base origin/main on a branch, then npx @coldtea/pr-lens-cli render .pr-lens/graph.json. Same lenses, your own model key, before the pull request exists.
  • Click the link under each diagram to open it on a canvas you can zoom, pan and step through.
  • The diagrams are links. Click one to open it on the canvas, then press W or click play to walk through the change.
  • Open a diagram on the canvas, then press W or click play to walk through the change one step at a time.
  • The CLI's render reads .github/pr-lens.yml and applies your renames, exclusions and lane pins at draw time.
  • Set github.comment.collapsed: true in .github/pr-lens.yml to fold the comment behind one View architecture and data flow row. Drawing still runs on every push.
  • Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and your model provider's key as its api-key to run PR Lens from your own CI. Any /chat/completions endpoint works.
  • Push a commit and the comment redraws for the new head. A slow older run never overwrites a newer one.
  • Switch GitHub to dark mode and the diagrams follow. The moving dots are this pull request's data in motion.

Thanks for using PR Lens! It's built by Coldtea, free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

@antfubot

Copy link
Copy Markdown
Collaborator

Update summary

Rebased this PR onto the 0.10 main and refined it. Four commits on top of 3e7f0fe6:

1. feat: expose in-page tools to coding agents via MCP (rebase onto 0.10 + #387)

  • Dropped this branch's own eager client-script implementation — it now lives in main via fix(hub-ui): initialize dock page scripts before activation #387 — and kept the browser-to-node agent bridge that turns in-page channel functions into MCP tools.
  • In-page channel functions can carry agent metadata; page-script/panel endpoints register those as browser-agent tools, mirror the manifest to node over an RPC bridge, and a node-side tool provider registers them on the agent host so they surface over MCP. Invocations round-trip back into the browser via devframe:agent:invoke-client-tool.
  • Adapted to the 0.10 MCP move: the discovery-metadata change now lands in @devframes/agentic's connect surface, the browser-safe JSON-schema/positional-arg helpers replace the pre-move paths, and the new diagnostic was renumbered to DF0080 (DF0078 now belongs to agentic) with its docs page.

2. refactor: infer JSON serialization for agent in-page functions and dedupe safety

  • Aligned the in-page agent option with fix: infer JSON serialization for agent RPCs #379: agent now implies jsonSerializable: true instead of requiring it; DF0080 only fires on an explicit jsonSerializable: false.
  • Collapsed the three copies of the agent safety inference (WebMCP, MCP host, in-page bridge) into one shared resolveAgentSafety helper, and removed a redundant id de-dup in the browser-agent registry (the node-side provider already dedupes).

3. feat: give each browser client a stable id reported to node (groundwork for #394)

  • Each browser tab mints a stable client id (sessionStorage-backed nanoid that survives reloads/reconnects) and tags its devframe:agent:sync-client-tools payload with it, so the node-side client-agent registry records which tab each session belongs to.
  • Identifier only — no per-tab listing or routing yet. Known caveat: tab duplication copies sessionStorage, so duplicated tabs can briefly share an id until the wider tab-metadata work adds disambiguation.

4. perf: load the browser-agent bridge lazily, only when MCP is enabled

  • The client-tool bridge is only useful when the node exposes an MCP endpoint, so it's gated on connectionMeta.mcp (already forwarded in __connection.json) and pulled in via a dynamic import(). The bridge, browser tool registry, and client-id helper now live in their own chunk that a non-MCP connection never downloads or runs.

Full validation passes on each step: build (incl. the client-dist chunk-reachability guard), pnpm test (141 files, 1569 passed / 9 skipped, no type errors), pnpm typecheck, pnpm lint, pnpm knip.

This update was prepared with the help of an agent.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved issues remain in MCP mounting, synchronization retries, codec handling, output metadata, and session reconciliation.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds eager exposure of agent-enabled in-page functions as browser-backed MCP tools, with browser-to-node synchronization and RPC invocation.

Changes:

  • Adds browser tool registration, lifecycle management, synchronization, and invocation.
  • Projects browser tools into node-side MCP agents with shared safety and metadata handling.
  • Updates tests, API snapshots, diagnostics, and documentation.
File summaries
File Summary
tests/__snapshots__/tsnapi/devframe/index.snapshot.d.ts Updates generated API declarations.
packages/devframe/src/types/rpc-augments.ts Declares browser-agent RPC methods.
packages/devframe/src/node/rpc/index.ts Registers manifest synchronization RPC.
packages/devframe/src/node/rpc/agent-sync-client-tools.ts Handles browser tool manifest synchronization.
packages/devframe/src/node/host-functions.ts Removes disconnected sessions.
packages/devframe/src/node/host-agent.ts Uses shared safety resolution.
packages/devframe/src/node/client-agent.ts Aggregates and routes browser tools.
packages/devframe/src/node/__tests__/client-agent.test.ts Tests browser tool projection and invocation.
packages/devframe/src/in-page-channel/types.ts Adds in-page agent metadata.
packages/devframe/src/in-page-channel/panel.ts Manages panel tool registration.
packages/devframe/src/in-page-channel/page-script.ts Manages page-script tool registration.
packages/devframe/src/in-page-channel/internal.ts Implements tool registration and invocation.
packages/devframe/src/in-page-channel/diagnostics.ts Adds the DF0080 diagnostic.
packages/devframe/src/in-page-channel/agent.test.ts Tests in-page agent behavior.
packages/devframe/src/client/webmcp.ts Reuses shared safety resolution.
packages/devframe/src/client/rpc.ts Loads the browser-agent bridge.
packages/devframe/src/client/rpc-auth-gate.test.ts Tests conditional bridge loading.
packages/devframe/src/client/client-id.ts Provides stable tab identifiers.
packages/devframe/src/client/browser-agent.ts Maintains the browser tool registry.
packages/devframe/src/client/browser-agent-rpc.ts Synchronizes and invokes browser tools.
packages/devframe/src/client/browser-agent-rpc.test.ts Tests browser-agent RPC behavior.
packages/devframe/src/agent/safety.ts Centralizes safety inference.
packages/agentic/src/connect/index.ts Preserves MCP tool metadata.
docs/content/6.errors/DF0080.md Documents the new diagnostic.
docs/content/1.guide/12.in-page-channel.md Documents in-page agent exposure.
Review details

Suppressed comments (4)

packages/devframe/src/client/browser-agent-rpc.ts:62

  • lastSyncedCount is updated before the sync result is known. If an empty-manifest sync fails while the RPC session stays alive, a later connected event sees manifests.length === 0 && lastSyncedCount === 0 and skips retrying, leaving the server's old tools exposed. Update the count only after callOptional succeeds.
      lastSyncedCount = manifests.length
      await rpc.callOptional('devframe:agent:sync-client-tools', resolveClientId(), manifests).catch(() => {})

packages/devframe/src/client/browser-agent-rpc.ts:67

  • connection:status is the canonical client event already defined in DEVFRAME_EVENTS.client.connectionStatus, and the transport code uses that map. Referencing the raw string here violates the repository's event-map contract and can let this listener drift if the event is renamed.
  const stopConnection = rpc.events.on('connection:status', (status) => {

packages/devframe/src/in-page-channel/internal.ts:290

  • registry.resolve() is the MessagePort receive pipeline: it applies the endpoint's deserialize hook to arguments and serialize hook to the result. Browser-agent calls arrive as already-JSON tool inputs and return directly to the MCP bridge, so a channel using a custom codec will transform the agent payload/result unexpectedly. Invoke through a local agent path that keeps JSON/schema validation but bypasses channel codecs.
      invoke: (args) => {
        const positional = toolInputToRpcArgs(args, definition.args?.length)
        return registry.resolve(channelMethod(definition.type, definition.name))!(...positional)

packages/devframe/src/in-page-channel/internal.ts:287

  • The in-page function's returns schema is dropped from the browser manifest here. The node agent projection uses tool.outputSchema to advertise structured output and populate MCP structuredContent, so an object-returning in-page tool is exposed as text-only despite declaring returns. Include the converted return schema in the manifest/provider projection.
      safety: resolveAgentSafety(definition.type, agent),
      tags: agent.tags,
      inputSchema: argsToJsonSchema(definition.args),
  • Files reviewed: 24/25 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +606 to +610
if (connectionMeta.mcp) {
void import('./browser-agent-rpc')
.then(({ setupBrowserAgentRpcBridge }) => {
if (!closed)
disposeBrowserAgentBridge = setupBrowserAgentRpcBridge(rpc)
Comment on lines +61 to +63
const state = getState(context)
state.sessions.set(session.meta, { session, clientId, tools })
state.notifyChanged()
@antfu
antfu merged commit 9464558 into main Sep 16, 2026
15 checks passed
@antfu
antfu deleted the eager-client-script branch September 16, 2026 03:48
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.

4 participants