Observable Notebook Kit Debug MCP Server
# mcp-observable-notebook-kit-debug
> MCP server for debugging [Observable Notebook Kit](https://github.com/observablehq/notebook-kit) notebooks.
Enables AI assistants to inspect values, view errors, and capture canvas output from notebooks running in a web browser. For a fully working example, see [./example](./example).
## Why?
The [Observable Desktop](https://observablehq.com/notebook-kit/desktop) is really cool, but it had some limitations. For one, okay, so I paid Anthropic some money for Claude Code, but it's not the *right* Anthropic integration to be able to configure an API key in the app, so I'm out of luck. And I was trying to get some WebGPU experiments running, but the app didn't have access to a WebGPU context. So one thing led to another, and I wrote a quick MCP server to exfiltrate values from notebooks running in the browser.
## Setup
### 1. Install the package
```bash
npm install @rreusser/mcp-observable-notebook-kit-debug
```
### 2. Add the Vite plugin
In your `vite.config.js`:
```js
import { defineConfig } from "vite";
import { observable, config } from "@observablehq/notebook-kit/vite";
import { debugNotebook } from "@rreusser/mcp-observable-notebook-kit-debug";
export default defineConfig({
...config(),
plugins: [debugNotebook(), observable()],
});
```
### 3. Run the dev server
```bash
vite -c vite.config.js
```
### 4. Configure the MCP server
Add to your `.mcp.json`:
```json
{
"mcpServers": {
"Notebook": {
"command": "mcp-notebook-kit-debug",
"args": []
}
}
}
```
Note that you might need an absolute path to the mcp-notebook-kit-debug bin, and you might be using opencode or some other tool, so this step could vary a bit.
### 5. Go!
You can now use an agent like Claude Code to poke and prod at notebooks running in a web browser, inspecting values and even capturing canvas output as images.
## MCP Tools
### Observable Runtime Tools
These tools interact directly with the Observable runtime's reactive graph.
| Tool | Description |
| ------------------------- | --------------------------------------------------------------------------- |
| `ListValues` | List all named values in the Observable runtime's reactive graph |
| `GetValue` | Get a value from the runtime by name; returns images for Canvas/SVG elements |
| `GetValues` | Get multiple values at once (snapshot of runtime state) |
| `GetValueMetadata` | Get metadata: state, type, dependencies (inputs), and dependents (outputs) |
| `GetDependencyGraph` | Get the dependency graph showing how values depend on each other |
| `SetInputValue` | Set an input widget's `.value` property and trigger reactive updates |
| `RuntimeEval` | Evaluate an expression with access to all notebook variables |
### Browser Tools
These tools interact with the browser/DOM context, outside the Observable runtime.
| Tool | Description |
| ------------------------- | --------------------------------------------------------------------------- |
| `BrowserEval` | Execute JavaScript in the browser (has DOM access but NOT Observable runtime) |
| `GetElementContent` | Get content from a DOM element by CSS selector; captures canvas/SVG as images |
| `MouseClick` | Simulate a mouse click at a position or on an element |
| `MouseDrag` | Simulate a mouse drag from start to end position |
| `MouseHover` | Simulate mouse hover at a position, triggering hover states and tooltips |
| `MouseWheel` | Simulate a mouse wheel scroll at a position |
| `SendKeys` | Simulate keyboard input to an element |
### Session Tools
These tools manage notebook connections and debug sessions.
| Tool | Description |
| ------------------------- | --------------------------------------------------------------------------- |
| `ListNotebooks` | List all connected notebooks (use when multiple notebooks are open) |
| `FocusNotebook` | Set a default notebook for subsequent commands (when multiple are connected) |
| `Refresh` | Refresh the page and wait for notebook initialization |
| `Navigate` | Navigate a notebook to a different URL (e.g., switch from notebook-a to notebook-b) |
| `GetConsoleMessages` | View console logs from the current session |
| `GetErrors` | Get all errors (DOM-reported and values in rejected state) |
### Multi-Notebook Support
When multiple notebooks are open in different browser tabs, you can target a specific notebook using the `notebook` parameter on any tool:
```
# By path (without .html extension)
notebook: "index"
notebook: "second-notebook"
# By index
notebook: "0"
notebook: "1"
# By URL
notebook: "http://localhost:5173/"
```
If multiple notebooks are connected and you don't specify which one, the tool will return an error listing the available notebooks.
Use `ListNotebooks` to see all connected notebooks with their URLs and indices.
### Navigating Between Notebooks
The `Navigate` tool allows you to navigate an open notebook to a different URL, or open a URL in your default browser if no notebooks are connected:
```javascript
// If no notebooks are connected, opens URL in default browser
Navigate({ url: "http://localhost:5173/index.html" })
// Navigate to a different notebook (relative URL)
Navigate({ url: "/second-notebook.html" })
// Navigate using absolute URL
Navigate({ url: "http://localhost:5173/second-notebook.html" })
// Navigate a specific notebook (when multiple are open)
Navigate({ url: "/second-notebook.html", notebook: "index" })
// Skip waiting for page load (useful if target page doesn't have debug plugin)
Navigate({ url: "/some-page.html", wait_for_completion: false })
```
**Behavior:**
- **No notebooks connected**: Opens the URL in your OS default browser
- **One notebook connected**: Navigates that notebook's browser window to the new URL
- **Multiple notebooks connected**:
- Navigates the focused notebook (if you've used `FocusNotebook`)
- Navigates the specified notebook (if `notebook` parameter provided)
- Returns an error listing all notebooks (if no focus is set and no notebook specified)
The tool waits for the new page to load and initialize, similar to `Refresh`, and reports any errors that occur during initialization.
### Value States
Values in an Observable notebook can be in one of three states:
- **fulfilled**: The value has been computed successfully
- **pending**: The value is still being computed (e.g., async/Promise)
- **rejected**: The computation threw an error
The `GetValue` and `GetValues` tools return state information along with the value or error.
### Image Support
`GetValue` automatically returns images inline for:
- **Canvas elements**: Captured as PNG
- **SVG elements**: Rendered to canvas and captured as PNG
No need to save to files - images are returned directly in the MCP response.
### Runtime Evaluation
Use `RuntimeEval` to evaluate expressions in the Observable runtime context with access to all notebook variables. The body must use a `return` statement.
```javascript
// Compute derived values from notebook variables
RuntimeEval({ body: "return number * 2 + rangeValue" })
// Filter or transform data
RuntimeEval({ body: "return data.filter(d => d.value > 0)" })
// Multi-statement expressions
RuntimeEval({
body: `
const doubled = number * 2;
const added = doubled + rangeValue;
return { doubled, added };
`
})
// Persist the result as a named variable for later retrieval
RuntimeEval({
name: "myResult",
body: "return number * 2"
})
```
Dependencies are auto-detected from the expression. If `name` is provided, the result persists in the runtime and can be retrieved with `GetValue`. If `name` starts with `_tmp_`, it is automatically deleted after the value resolves.
### Setting Input Values
Use `SetInputValue` to programmatically change the value of interactive input widgets created with `Inputs.*` (e.g., `Inputs.range`, `Inputs.select`, `Inputs.text`). This sets the widget's `.value` property and dispatches an input event, triggering reactive updates to dependent values.
```javascript
// If the notebook has: slider = Inputs.range([0, 100])
SetInputValue({ name: "slider", value: 50 })
// If the notebook has: dropdown = Inputs.select(["A", "B", "C"])
SetInputValue({ name: "dropdown", value: "B" })
```
### Mouse Interaction
Simulate mouse events for testing interactive visualizations:
- **`MouseClick`**: Click at coordinates or on an element (supports left/middle/right buttons)
- **`MouseDrag`**: Drag from start to end position with configurable duration
- **`MouseHover`**: Hover at a position, dispatching mouseenter/mouseover/mousemove events
- **`MouseWheel`**: Scroll at a position with deltaX/deltaY
All mouse tools accept an optional `selector` parameter to target a specific element, with coordinates relative to that element.
### Keyboard Interaction
Use `SendKeys` to simulate keyboard input:
```javascript
// Type plain text
SendKeys({ keys: "hello world" })
// Use special keys with braces
SendKeys({ keys: "{Enter}" })
SendKeys({ keys: "{Tab}" })
SendKeys({ keys: "{ArrowDown}" })
// Modifier combinations
SendKeys({ keys: "{Ctrl+a}" }) // Select all
SendKeys({ keys: "{Ctrl+c}" }) // Copy
SendKeys({ keys: "{Shift+Tab}" }) // Reverse tab
// Target a specific element
SendKeys({ selector: "#my-input", keys: "typed text{Enter}" })
// Hold modifiers for all keys
SendKeys({ keys: "abc", modifiers: { shiftKey: true } }) // Types "ABC"
```
Supported special keys: `{Enter}`, `{Tab}`, `{Escape}`, `{Esc}`, `{Backspace}`, `{Delete}`, `{Insert}`, `{Space}`, `{ArrowUp}`, `{ArrowDown}`, `{ArrowLeft}`, `{ArrowRight}`, `{Home}`, `{End}`, `{PageUp}`, `{PageDown}`, `{F1}`-`{F12}`.
## License
© 2026 Ricky Reusser. MIT License.
TDQS
Scored across 20 tools
Tools are largely purpose-specific: value retrieval is split into single/bulk/metadata/list, and evaluation is split into browser vs runtime contexts. A few boundaries require care—GetErrors vs GetConsoleMessages both surface errors, and GetValues with no names overlaps with ListValues—but descriptions clarify intended usage.
Most tools follow a PascalCase VerbNoun pattern (ListNotebooks, GetValue, SetInputValue, SendKeys). Exceptions break the pattern: Refresh and Navigate are bare verbs, while BrowserEval/RuntimeEval and MouseClick/Drag/Wheel/Hover put the context or object before the action, making the convention less predictable.
20 tools is at the high end for a debugger and feels slightly heavy, with multiple value-retrieval variants and five input-simulation tools. The breadth is defensible because the server covers runtime state, DOM inspection, and mouse/keyboard interaction, but it is borderline rather than clearly well-scoped.
The server covers the main notebook debugging loop: discover/connect notebooks, inspect runtime values and dependencies, read console/errors, manipulate inputs, and simulate interactions. Obvious gaps such as a dedicated screenshot or wait-for-condition tool are workaroundable via BrowserEval/RuntimeEval, but they are minor omissions.