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
2 changes: 1 addition & 1 deletion .github/workflows/release-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ jobs:
if: github.repository == 'vuejs/devtools'
uses: sxzz/workflows/.github/workflows/release-commit.yml@v1
with:
packages: "'./packages/kit' './packages/devtools-api' './packages/vite'"
packages: "'./packages/kit' './packages/agent' './packages/devtools-api' './packages/vite'"
compact: true
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Vue DevTools v9 ships two hosts over one kit and one client. The Vite plugin is

- **`@vue/devtools-kit` + `@vue/devtools-client`** — shared runtime and UI: component tree, state, timeline, Pinia, pages, reactivity graph, and the v6 plugin setup API (`setupDevToolsPlugin`, alias `setupDevtoolsPlugin`).
- **`vite-plugin-vue-devtools`** — Vite host. Requires Vite 8.3.0+ and a project-level `@vitejs/devtools` (`devtools: { apply: 'serve' }` is recommended) next to `plugins: [vueDevTools()]`. This package does not register `DevTools()` and does not brand the shared Vite shell. Sibling repo: `../vite-devtools`. Read its [AGENTS.md](../vite-devtools/AGENTS.md) before changing dock, command, or open-in-editor behavior.
- **`@vue/devtools-agent`** — agent (MCP) tools over the kit's runtime requests: apps, component tree and state, inspectors and Pinia, router, value expansion, and guarded state edits. The Vite host registers them through Devframe (`./node` for the host help tool, `./devframe` for per-page tools); `vue-devtools-agent connect` exposes running Devframe instances over stdio MCP. The Chromium extension does not register agent tools.
- **`@vue/devtools-chrome`** — Chromium MV3 host. Content backend calls `createDevtoolsKit({ clientName: 'chrome-extension' })` on the page and bridges the panel on channel `vue-devtools:chrome-extension`. It works on any page that runs Vue, including apps that never load Vite DevTools.

`devframe` / `@devframes/hub` (docs: [devfra.me](https://devfra.me)) matter on the Vite path: docks, commands, terminals, messages. The extension does not use that hub. Features that only exist when several Vite tools share a UI (custom tabs, commands, split screen, the floating inspector button) belong in Vite DevTools. Vue-level `addCustomTab` / `addCustomCommand` stay removed on both hosts. Custom inspectors, component hooks, timeline layers, and plugin settings stay on the Vue plugin API.
Expand All @@ -22,6 +23,7 @@ Monorepo (`pnpm` workspaces). ESM TypeScript. Library packages bundle with tsdow
| `packages/devtools-api` | `@vue/devtools-api` | Public plugin API surface over the kit. |
| `packages/client` | `@vue/devtools-client` | Vue 3 + UnoCSS UI shared by both hosts. Private. Vite plugin copies the build into `packages/vite/client`; the extension loads the same client in its panel. |
| `packages/vite` | `vite-plugin-vue-devtools` | Vite host. Serves the static client and registers an iframe dock with `@vitejs/devtools`. |
| `packages/agent` | `@vue/devtools-agent` | Agent tools over the kit runtime, Devframe page/host adapters, and the `vue-devtools-agent` stdio MCP CLI. Used by the Vite host only. |
| `packages/chrome` | `@vue/devtools-chrome` | Chromium MV3 host (background, content backend, DevTools panel). Private. Zip via `pnpm zip:chrome`. |

Other top-level directories:
Expand All @@ -39,6 +41,8 @@ flowchart TD
client --> chrome["chrome extension"]
kit --> plugin
kit --> chrome
kit --> agent["@vue/devtools-agent"]
agent --> plugin
plugin --> viteHost["@vitejs/devtools"]
viteHost --> hub["@devframes/hub"]
hub --> devframe
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const GETTING_STARTED = [

const GUIDES = [
{ text: 'Vite Plugin', link: '/guide/vite-plugin' },
{ text: 'Agents', link: '/guide/agents' },
{ text: 'Browser Extension', link: '/guide/browser-extension' },
] satisfies DefaultTheme.NavItemWithLink[]

Expand Down
198 changes: 198 additions & 0 deletions docs/guide/agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Agents

Connect a coding agent to your running Vue app to inspect components, read Pinia stores, check routes, and edit individual state fields through MCP (Model Context Protocol).

Agents use the same runtime as the Vue DevTools panel. Keep your app open in the browser; the panel can stay closed. This integration is available during development through the Vite plugin. The [Chromium extension](/guide/browser-extension) does not expose an agent connection.

## Get started

### 1. Enable Vue DevTools

Set up the [Vite plugin](/guide/vite-plugin) with Vite 8.3 or later and `@vitejs/devtools` 0.7.6 or later. Start your dev server, open the app in a browser, and allow the Vite DevTools connection when prompted.

Vue's tools register automatically when the host provides MCP access. Setting `devtools.mcp` to `false` disables that access.

### 2. Configure your agent

Add the connector to your agent client's MCP configuration. Use the global configuration if you want to connect to multiple projects; the file location depends on the client.

```json
{
"mcpServers": {
"vue-devtools": {
"command": "npx",
"args": ["-y", "@vue/devtools-agent", "connect"]
}
}
}
```

The connector discovers running projects automatically, so you do not need to configure a port for each one.

### 3. Ask about your app

Try a request such as:

> Find the component that renders the user profile and inspect its current props and setup state.

The agent finds the running project, identifies the browser page, and queries its Vue tools. If you have several projects or pages open, include the project name or page URL in your request.

## Inspect the running app

An agent starts with these calls:

1. Call `devframe_connect_list-instances` and select the project by `rootDir`.
2. Find the page's Vue tools and call `list-apps` to get its URL and app IDs.
3. Call `component-tree` to find a component, then `component-state` to read its state.

Page tools are called through `devframe_connect_call-tool`. Use the port and exact tool name returned by discovery. An options object is passed as `args.arg0`:

```json
{
"port": 5173,
"tool": "<component-state tool name from discovery>",
"args": {
"arg0": {
"appId": "<app ID from list-apps>",
"componentId": "<component ID from component-tree>"
}
}
}
```

Each browser document has its own tools. Use `list-apps` to identify the page before inspecting it. After a reload, discover its tools again: the previous tool namespace, component IDs, and value handles are no longer valid.

### Available tools

| Task | Tools |
| ------------------------------------------ | ----------------------------------------------------------------------------- |
| Identify apps and check runtime health | `list-apps`, `list-plugins`, `runtime-health` |
| Browse the component tree | `component-tree`, `component-children`, `cancel-component-children` |
| Read component state and render code | `component-state`, `component-value`, `expand-value`, `component-render-code` |
| Inspect Pinia stores and other plugin data | `list-inspectors`, `inspector-tree`, `inspector-state`, `inspector-value` |
| Check routes | `router-state`, `router-matches` |
| Edit a state field | `edit-component-state`, `edit-inspector-state` |

Tools that target an app require an `appId`. Each tool declares an output schema. Successful calls return `{ page, capturedAt, result }`; failed calls use the MCP tool error channel.

The component tree loads incrementally. Without a filter, `component-tree` returns the initially expanded nodes. With a filter, it searches the whole app by component name or source file and returns up to 100 matches. To walk the tree without a filter, use `component-children` and pass the returned cursor to read the next page. Cancel unfinished reads with `cancel-component-children`. If a cursor expires, restart `component-children` without it.

Pinia stores and other plugin data are available through **Inspectors**. Call `list-inspectors`, find a node with `inspector-tree`, then read it with `inspector-state`.

For routing, `page.url` tracks the browser's URL, while `router-state` reports Vue Router's current route and registered routes. Use `router-matches` to check which route records match a path without navigating.

## Read a specific field

Use `component-state` for an overview and `component-value` when you know the field path. A direct field read does not require a previous snapshot or value handle and can reach fields outside the current page.

For example, to read `user.name` from a component's setup state, pass this options object to `component-value` as `args.arg0`:

```json
{
"appId": "<app ID>",
"componentId": "<component ID>",
"sectionId": "setup",
"path": ["user", "name"]
}
```

Get the section ID from `component-state`. Built-in sections include `props`, `data`, and `setup`. Use strings for array indexes, such as `["users", "0", "name"]`.

For plugin state, use `inspector-value` with `appId`, `inspectorId`, `nodeId`, `sectionId`, and `path`. The path starts with the state entry's key. The plugin's state hook still runs, but only the requested value is encoded.

Both tools return `found: false` if the target or path is missing. An existing field whose value is `null` or `undefined` returns `found: true`, with the value's type preserved by the Vue DevTools codec.

## Edit a field

Editing uses the same Kit commands and plugin hooks as the Vue DevTools panel:

1. Read the field with `component-value` or `inspector-value` and check that `found` and `editable` are both `true`.
2. Call `edit-component-state` or `edit-inspector-state` with the same target and path, plus a JSON `value`.
3. Check `result.state` for the value read back after the edit.

For example, pass the following options to `edit-component-state` to update `user.name`:

```json
{
"appId": "<app ID>",
"componentId": "<component ID>",
"sectionId": "setup",
"path": ["user", "name"],
"value": "Ada"
}
```

A `status: 1` response acknowledges the command; it does not guarantee that the requested value was retained. A plugin may transform the value or leave it unchanged, and a parent render may overwrite an edited prop. Check the returned state to see the actual result.

These tools edit existing fields. They cannot delete or rename fields, navigate, or invoke arbitrary plugin actions. Component field tools work with built-in state sections; access custom plugin state through its Inspector.

If an edit encounters a connection or readback error, read the field before retrying. The change may already have been applied.

## Read all state with pagination

`component-state` and `inspector-state` return the first page of fields, grouped by section. To read everything, follow `pagination.next` until it is `null`:

```json
{
"snapshotId": "state-page:1",
"pagination": {
"total": 126,
"current": 1,
"next": 2
},
"sections": []
}
```

This example shows the pagination metadata; actual responses include the page's entries in `sections`. `total` counts fields across all sections, not pages. `current` and `next` are page numbers starting at 1.

For the next call, keep the original query parameters, pass back `snapshotId`, and set `page` to the returned `next`. For example, the next `component-state` call uses:

```json
{
"appId": "<app ID>",
"componentId": "<component ID>",
"page": 2,
"snapshotId": "state-page:1"
}
```

Pages can contain different numbers of entries. Always follow `next` to determine whether more data is available. A section's `partial` flag means this page contains only part of that section; it does not mean the remaining fields are inaccessible.

The same pagination parameters work with `expand-value`. For `component-value` and `inspector-value`, omit them for a compact preview, or pass `page: 1` to start paging through the selected value. Collections return a page of preview entries. Strings return text segments: `total` counts UTF-16 code units, and concatenating the segments reconstructs the full string.

A batch keeps the original field order, but it is not an atomic snapshot of the application. Object properties can change as they are read. For `inspector-state`, the plugin hook runs when the batch starts. Component reactivity graphs, when available, appear on the first state page. Read pages in order; retrying the most recent page returns the same result. To start over, omit `snapshotId` and request page 1. Batches are retained temporarily; an expired ID returns instructions to restart.

### Value handles

A **value handle** is a temporary reference to an object or array. Pass it to `expand-value`, optionally with a property path, to read details omitted from a preview. A missing path produces an error; an existing `undefined` value is returned with its codec tag.

Handles can expire when the same component or Inspector node is read again, including by the DevTools panel, or when the runtime releases retained values. Keeping the connection open does not keep handles valid. To recover, start a fresh state query and use its new handles, or read a known field path directly. After a page reload, rediscover the page tools as well.

## Troubleshooting

| Problem | What to do |
| ---------------------------- | -------------------------------------------------------------------------------------------------- |
| Project not found | Check that the dev server is running, or specify its port as shown below. |
| Page tools missing | Open the app in a browser and allow the Vite DevTools connection. Rediscover tools after a reload. |
| Field returns `found: false` | Check the target IDs, section ID, and field path. |
| Field is read-only | Choose a field marked `editable: true`. |
| Value handle expired | Read the state again to get a new handle, or use a field path. |
| Response is too large | Read a narrower field path if a single entry is too large. |
| Edit result is uncertain | Read the current value before retrying. |

To find a project by port:

```sh
npx @vue/devtools-agent connect --port 5173 --base /__devtools/
```

Repeat `--port` or pass a comma-separated list, such as `--port 5173,5174`, to probe several ports.

## Connection and access

Vite DevTools manages discovery, the MCP endpoint, and authentication. The default host accepts callers on the same machine. The connector sends a bearer token when `DEVFRAME_MCP_AUTH_TOKEN` is set. See [Devframe MCP](https://devfra.me/adapters/mcp) for host configuration.

The Vue runtime connection opens on the first query and stays open until the page's tools are unregistered. Discovery alone does not open it.

Connected agents can read application state, including any private data it contains. As in the Vue DevTools panel, reading state may invoke application getters and plugin hooks.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"test:integration": "vitest run tests/integration",
"test:performance": "vitest run --config vitest.performance.config.ts",
"test:smoke:chrome": "pnpm --filter @vue/devtools-kit build && pnpm --filter @vue/devtools-client build && pnpm --filter @vue/devtools-chrome build && vitest run --config vitest.smoke.config.ts",
"test:smoke:vite": "pnpm --filter @vue/devtools-kit build && pnpm --filter @vue/devtools-client build && pnpm --filter vite-plugin-vue-devtools build && vitest run --config vitest.smoke.config.ts tests/smoke/vite-client-injection.test.ts",
"test:smoke:vite": "pnpm --filter @vue/devtools-kit build && pnpm --filter @vue/devtools-client build && pnpm --filter @vue/devtools-agent build && pnpm --filter vite-plugin-vue-devtools build && vitest run --config vitest.smoke.config.ts tests/smoke/vite-client-injection.test.ts tests/smoke/agent.test.ts",
"test:unit": "vitest run tests/unit",
"test:watch": "vitest",
"typecheck": "vue-tsc --noEmit -p packages/client/tsconfig.json",
Expand All @@ -50,6 +50,7 @@
},
"devDependencies": {
"@clack/prompts": "catalog:",
"@modelcontextprotocol/client": "catalog:",
"@types/chrome": "catalog:",
"@types/node": "catalog:",
"@vitejs/devtools": "catalog:",
Expand Down
21 changes: 21 additions & 0 deletions packages/agent/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 webfansplz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading