Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions docs/content/1.guide/15.agent-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
title: 'Agent-Native Devframe'
navigation:
icon: i-lucide-bot
description: 'Devframe exposes its browser-side API (RPC functions, resources, shared state) to coding agents over MCP, opt-in per function.'
description: 'Devframe exposes its API (RPC functions, resources, shared state) to agents, over MCP on the node side and WebMCP on the browser side, opt-in per function.'
---

Devframe exposes its browser-side API (RPC functions, resources, shared state) to coding agents over MCP, opt-in per function.
Devframe exposes its API (RPC functions, resources, shared state) to agents, over MCP on the node side and [WebMCP](#browser-side-tools-over-webmcp) on the browser side, opt-in per function.

## How it works

Three pieces: the **`agent` field** on `defineRpcFunction`, **`ctx.agent`** (non-RPC tools + resources), and the **MCP adapter** (`devframe/adapters/mcp`) serving an [MCP](https://modelcontextprotocol.io) server.
Three pieces: the **`agent` field** on `defineRpcFunction`, **`ctx.agent`** (non-RPC tools + resources), and the **MCP adapter** (`devframe/adapters/mcp`) serving an [MCP](https://modelcontextprotocol.io) server. The same `agent` field on a *client* RPC function surfaces it [over WebMCP](#browser-side-tools-over-webmcp) instead.

## Exposing an RPC function

Expand Down Expand Up @@ -137,6 +137,29 @@ In `claude_desktop_config.json`:

Restart; tools appear in the drawer, resources as `devframe://resource/<id>` / `devframe://state/<key>` URIs.

## Browser-side tools over WebMCP

The same `agent` signature works on the browser side: a client RPC function (a function the node side calls on the browser, registered on `rpc.client` or through a scoped `client.scope('my-plugin').rpc.register(...)`) carrying an `agent` field is mirrored onto the page's [WebMCP](https://github.com/webmachinelearning/webmcp) model context (`document.modelContext` / `navigator.modelContext`) as a callable tool, so in-page and browser-integrated agents can drive browser-side functionality directly. Wire names, `arg0`/`arg1`/… input schemas, and safety annotations match the MCP projection above.

```ts
const rpc = await connectDevframe()

rpc.client.register({
name: 'my-plugin:highlight-node',
type: 'action',
jsonSerializable: true,
agent: {
description: 'Highlight a node in the open inspector view. Use it to point the user at a finding.',
},
handler: (id: string) => highlightNode(id),
})
```

`connectDevframe()` wires this on its own when the browser provides a model context; `webmcp: false` keeps the browser side off the WebMCP surface. `registerWebMcpTools(collector)` (from `devframe/client`) applies the same projection to a hand-built collector and returns a dispose that unregisters every tool.

> [!WARNING]
> WebMCP is an experimental proposal; `registerWebMcpTools` tracks the current draft (`AbortSignal`-based unregistration) and earlier handle-returning drafts, but the browser API may still change.

## Writing descriptions agents act on

Describe *when* to use a tool, not just its return:
Expand Down
1 change: 1 addition & 0 deletions docs/content/5.add-ons/1.devframes/2.inspect.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ _History panels_
## What it does

- **Functions**: type, flags, JSON Schema, agent exposure; read-only `query` / `static` invokable inline.
- **Client**: the browser side of the connection: client RPC functions (invoked locally in the page) and the page's WebMCP tools, live from the model context's `getTools()` when the browser supports discovery, otherwise projected from `agent`-flagged client functions.
- **State**: shared-state keys in a live JSON tree that flashes changes.
- **Agent**: tools and resources for agents.
- **History**: a timeline of RPC calls and shared-state updates.
Expand Down
1 change: 1 addition & 0 deletions docs/content/8.references/5.browser-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The options of `connectDevframe()` / `getDevframeRpcClient()`: [Client](/guide/c
| `wsOptions` | Transport overrides: `onConnected` / `onError` / `onDisconnected` hooks, socket URL. |
| `rpcOptions` | Forwarded to `birpc`. |
| `connectionMeta` | Descriptor that skips the `__connection.json` fetch. |
| `webmcp` | Mirror `agent`-flagged client RPC functions onto the page's WebMCP model context as tools; `false` opts out. Default `true` (applies only when the browser provides one). See [Agent-Native](/guide/agent-native#browser-side-tools-over-webmcp). |

## RPC client events

Expand Down
1 change: 1 addition & 0 deletions packages/devframe/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export * from './rpc-streaming'
export { resolveWsUrl, type WsUrlLocation } from './rpc-ws'
export * from './scope'
export * from './settings'
export * from './webmcp'

export const connectDevframe = getDevframeRpcClient
20 changes: 19 additions & 1 deletion packages/devframe/src/client/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { createStaticRpcClientMode } from './rpc-static'
import { createRpcStreamingClientHost } from './rpc-streaming'
import { createWsRpcClientMode } from './rpc-ws'
import { createScopedClientContext } from './scope'
import { registerWebMcpTools } from './webmcp'

export interface DevframeRpcContext {
/**
Expand Down Expand Up @@ -99,6 +100,18 @@ export interface DevframeRpcClientOptions extends SetupDevframeConnectionOptions
sseOptions?: Partial<SseRpcChannelOptions>
rpcOptions?: Partial<BirpcOptions<DevframeRpcServerFunctions, DevframeRpcClientFunctions, boolean>>
cacheOptions?: boolean | Partial<RpcCacheOptions>
/**
* Mirror `agent`-flagged client RPC functions (functions registered on
* `rpc.client` with an `agent` field) onto the page's WebMCP model
* context (`document.modelContext` / `navigator.modelContext`) as
* callable tools, so in-page and browser-integrated agents can invoke
* them; see `registerWebMcpTools`. Applies only when the browser
* provides a model context. Set `false` to keep the browser side off
* the WebMCP surface.
*
* @default true
*/
webmcp?: boolean
/**
* Reject a pending `rpc.call(...)` if the server hasn't answered within this
* many milliseconds, with a {@link DevframeConnectionError} of kind
Expand Down Expand Up @@ -332,6 +345,8 @@ export async function getDevframeRpcClient(
rpc: undefined!,
}
const clientRpc: DevframeClientRpcHost = new RpcFunctionsCollectorBase<DevframeRpcClientFunctions, DevframeRpcContext>(context)
// No-op when the browser provides no WebMCP model context.
const disposeWebMcp = options.webmcp === false ? undefined : registerWebMcpTools(clientRpc)

async function fetchJsonFromBases(path: string): Promise<any> {
const candidates = [
Expand Down Expand Up @@ -470,7 +485,10 @@ export async function getDevframeRpcClient(
streaming: undefined!,
cacheManager,
scope: undefined!,
close: () => mode.close?.(),
close: () => {
disposeWebMcp?.()
mode.close?.()
},
}

rpc.sharedState = createRpcSharedStateClientHost(rpc)
Expand Down
Loading
Loading