A debug bridge embedded inside your web app

Feedthrough

by feedthrough · MIT license · ★ 4 on GitHub · updated Sep 4

Feedthrough cover

A debug bridge embedded inside your web app, exposing runtime internals to AI agents via MCP.

# Feedthrough

**Debug with AI — from inside your app.**

Feedthrough injects a lightweight debug bridge into any running web page, then exposes everything
— DOM state, console logs, network requests, and user interactions — as MCP tools. Any
MCP-compatible AI agent can inspect and drive the page conversationally, in real time.

```
Browser (any)
 └── @feedthrough/core          ← injected into your page
      ├── console interceptor
      ├── fetch / XHR interceptor
      └── DOM inspector
      ↕  WebSocket
@feedthrough/mcp               ← MCP server, exposes tools over stdio
 └── Tools: click, fill, inspect_element, query_dom,
            get_console_logs, get_network_requests, …

Overview

Feedthrough injects a lightweight debug bridge into any running web page, then exposes everything — DOM state, console logs, network requests, and user interactions — as MCP tools. Any MCP-compatible AI agent can inspect and drive the page conversationally, in real time.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • README.md16.0 KB
Read README.md here
feedthrough/main1 file
README.md
Show the full text407 lines

Feedthrough

Debug with AI — from inside your app.

Feedthrough injects a lightweight debug bridge into any running web page, then exposes everything — DOM state, console logs, network requests, and user interactions — as MCP tools. Any MCP-compatible AI agent can inspect and drive the page conversationally, in real time.

Browser (any)
 └── @feedthrough/core          ← injected into your page
      ├── console interceptor
      ├── fetch / XHR interceptor
      └── DOM inspector
      ↕  WebSocket
@feedthrough/mcp               ← MCP server, exposes tools over stdio
 └── Tools: click, fill, inspect_element, query_dom,
            get_console_logs, get_network_requests, …
      ↕  MCP protocol
Claude Code / Cursor / any MCP client

The name

Many physics and chemistry experiments run inside a sealed vacuum chamber, with all the air pumped out so nothing contaminates the experiment. The catch: you still need to control instruments inside the chamber and read their measurements, and the smallest air leak ruins the run. A feedthrough is the part that solves this — a specially engineered connector that carries electrical signals through the chamber wall while keeping the vacuum perfectly intact. You can't reach inside, but the feedthrough lets you observe and control what's happening in there anyway.

The parallel is exact: Feedthrough extracts runtime debug data from inside a running web app without disturbing it, and sends control signals back in — clicks, keystrokes, DOM queries — without breaking the execution environment.


Why Feedthrough?

Every other browser MCP tool is an external observer — it controls the browser from outside via Puppeteer or CDP and only works in Chrome. Feedthrough is an embedded agent. It runs inside the page, so it sees:

  • Framework internals (React component trees, Redux store, custom globals)
  • Any browser, not just Chrome
  • Your existing dev workflow — no separate controlled browser to launch
  • Cypress's own browser context during test runs

Packages

Package Description
@feedthrough/core In-browser bridge — intercepts console, fetch, XHR; handles commands
@feedthrough/mcp MCP server — bridges any MCP client to the browser via WebSocket
@feedthrough/cypress Cypress adapter — auto-injects the bridge before each test page load
@feedthrough/playwright Playwright adapter — injects the bridge via page.addInitScript()
@feedthrough/vite Vite plugin for apps with a static index.html
@feedthrough/webpack Webpack plugin — adds bridge as a global entry point
@feedthrough/nextjs Next.js adapter — wraps next.config.ts with withFeedthrough()
@feedthrough/nuxt Nuxt 3 module
@feedthrough/sveltekit SvelteKit adapter — injects via the handle hook
@feedthrough/remix Remix adapter — injects via a Vite dev server middleware

Framework support

Framework Adapter Notes
Vite + React / Vue / Solid / Preact @feedthrough/vite Static index.html — plugin uses transformIndexHtml
Next.js @feedthrough/nextjs Wraps the webpack config; dev only
Nuxt 3 @feedthrough/nuxt Registers as a Nuxt module; dev only
SvelteKit @feedthrough/sveltekit handle hook with transformPageChunk; dev only
Remix @feedthrough/remix Vite dev server middleware; dev only
Webpack apps @feedthrough/webpack Global entry point; guards against production mode
Cypress @feedthrough/cypress window:before:load hook
Playwright @feedthrough/playwright page.addInitScript()

Quick start

1. Start the MCP server
npx @feedthrough/mcp

The server listens for browser connections on ws://127.0.0.1:8765 and exposes MCP tools on stdio. Override the port with FEEDTHROUGH_PORT=9000. If the port is already taken, the server steps up to the next free one rather than refusing to start — see Running several sessions at once.

2. Add it to your MCP client config
{
  "mcpServers": {
    "feedthrough": {
      "command": "npx",
      "args": ["@feedthrough/mcp"]
    }
  }
}
3. Inject the bridge into your page

Vite + React / Vue / Solid / Preact:

// vite.config.ts
import { feedthrough } from "@feedthrough/vite";
export default defineConfig({ plugins: [feedthrough()] });

Next.js:

// next.config.ts
import { withFeedthrough } from "@feedthrough/nextjs";
export default withFeedthrough()({ /* your next config */ });

Nuxt 3:

// nuxt.config.ts
export default defineNuxtConfig({ modules: ["@feedthrough/nuxt"] });

SvelteKit:

// src/hooks.server.ts
import { feedthroughHandle } from "@feedthrough/sveltekit";
import { sequence } from "@sveltejs/kit/hooks";
export const handle = sequence(feedthroughHandle);

Remix:

// vite.config.ts
import { feedthrough } from "@feedthrough/remix";
export default defineConfig({ plugins: [remix(), feedthrough()] });

Webpack:

// webpack.config.mjs
import { FeedthroughPlugin } from "@feedthrough/webpack";
export default { plugins: [new FeedthroughPlugin()] };

Cypress:

// cypress/support/e2e.ts
import { setupFeedthrough } from "@feedthrough/cypress";
setupFeedthrough();

Playwright:

// import test from the adapter instead of @playwright/test
import { test, expect } from "@feedthrough/playwright";

Or manually (any bundler):

// main.ts
if (import.meta.env.DEV) {
  import("@feedthrough/core").then(({ init }) => init());
}
4. Open your page and start asking

Once the bridge connects you'll see [feedthrough] tab connected in the MCP server output. For the simplest experience, keep a single tab open. Multiple tabs can connect at the same time and commands are routed to the most recently active one, but a single tab avoids any ambiguity.

Then ask your AI agent:

> What's on the page right now?
> Click the submit button and tell me what network requests fired
> Why is the counter showing the wrong value?

MCP tools

Tool Description
get_instructions() Usage guide — recommended workflow, tool ordering, and selector tips
query_dom(selector) All elements matching a CSS selector
inspect_element(selector, properties?) Tag, attributes, full bounding rect + inViewport, ancestor path, curated computed styles, overflow info (clipped/overflowing content), clipped-by-ancestor info, effective visibility (visible + hiddenReason, accounting for ancestors), occlusion (hittable + occludedBy), accessibility (a11y: role, name, states), pseudo ::before/::after content, live form state; properties reads extra CSS props by name
get_html(selector) Raw outerHTML of a region (capped at 50 KB)
get_console_logs(limit?, levels?, match?, since?) Console output (all methods) plus uncaught errors & promise rejections; filter by levels, match, or since timestamp
get_network_requests(filter?, since?) Captured fetch + XHR — URL, method, status, duration, headers, request/response bodies (10 KB cap); narrow by filter or since
get_page_info() URL, title, readyState, viewport size, scroll position, user agent, and which bridge this page is connected to
connection_status() Connected tabs and which one is active, plus this server's name, version, and bound port
click(selector) Click an element via native click() (fires click + default activation, not the pointer sequence)
fill(selector, value) Set an input/textarea/select value (fires input + change, not keystrokes)
hover(selector) Fire mouseover/mouseenter to mount hover UI (JS handlers, not CSS :hover)
press_key(selector, key) Dispatch a key press — Enter, Escape, Tab, arrow keys, or a character
set_style(selector, properties) Preview a visual fix — set inline CSS live (not saved to source)
set_attribute(selector, name, value) Preview an attribute change — toggle disabled, swap a class, set aria-* (null removes)
set_text(selector, text) Preview wording/label changes — replace an element's text
reset_overrides() Undo every live set_style / set_attribute / set_text change

Live edit is a preview, not a save. set_style / set_attribute / set_text mutate the running DOM so the agent can show you a fix without a rebuild. They are not written to your source and reset on reload/HMR. The loop: the agent previews live, you confirm, then it edits the actual source to make it stick. Changes a framework owns (text, controlled attributes) may be overwritten on the next render — the tool result flags this so the agent can tell you.


Example app

examples/react-app is a small React app with three deliberate bugs — a good sandbox for trying out the diagnostic workflow:

# Terminal 1 — app
cd examples/react-app && pnpm dev    # http://localhost:5173

# Terminal 2 — MCP server
cd packages/mcp && node dist/index.js

Connect an AI agent and ask it to find what's wrong. The three bugs are all invisible from the UI but findable in under a minute via get_console_logs, get_network_requests, and query_dom.


Running several sessions at once

Two AI agent sessions on one machine each start their own Feedthrough MCP server, and only one of them can have port 8765. That is handled, but it is worth knowing how.

The server moves, and tells you where it went. On a busy port it steps up (8766, 8767, …) instead of failing. connection_status() reports the port it actually bound, and the agent passes that to the dev server it starts:

FEEDTHROUGH_PORT=8766 npm run dev

Every build-tool adapter — vite, webpack, nextjs, nuxt, sveltekit, remix — reads FEEDTHROUGH_PORT (or FEEDTHROUGH_URL for a full ws:// URL) in Node at config-load time and bakes the result into the injected bridge. So a committed, argument-free feedthrough() pairs correctly in any session, with no file edits. An explicit serverUrl option always wins over the environment.

If you start the dev server yourself, the environment is unset and the page falls back to 8765, which may be another session's server. Either export the port before starting it, or pin one per project (below).

Pinning a port per project. For a project you always work on in its own session, pin the port on both ends and neither has to think about it. In .mcp.json:

{
  "mcpServers": {
    "feedthrough": {
      "command": "npx",
      "args": ["@feedthrough/mcp"],
      "env": { "FEEDTHROUGH_PORT": "8770" }
    }
  }
}

and in the app's config, feedthrough({ serverUrl: "ws://localhost:8770" }).

Telling bridges apart. Each server picks a readable name at startup (quiet-olive-heron) and sends it to every page that connects. The page logs one line to the browser console, stores it on window.__feedthrough.server, and returns it from get_page_info(). The agent's own name comes from connection_status(). Two different names mean the tab is paired with another session's server — so "am I driving the right app?" is one call, not something you notice by watching the wrong window change.


Using with an AI agent

  1. connection_status() — confirm the bridge is connected before anything else
  2. get_console_logs() — errors and app output often identify the root cause immediately
  3. get_network_requests() — look for failed fetches, wrong URLs, or missing calls
  4. query_dom(selector) — find elements and check what's rendered
  5. inspect_element(selector) — deep-dive on a specific element
  6. click() / fill() — interact, then re-check logs and network
Project-memory snippet

Add this to whatever project-memory file your AI agent reads — CLAUDE.md for Claude Code, .cursor/rules/*.md for Cursor, and so on — to prime it with the right workflow:

## Debugging with Feedthrough

A Feedthrough MCP server is configured. When investigating UI bugs:

1. Call `connection_status()` first — fail fast if no browser is connected.
2. Check `get_console_logs()` before touching the DOM.
3. Check `get_network_requests()` for failed or missing API calls.
4. Use `query_dom` to orient yourself, `inspect_element` to dig into a specific element.
5. Interact with `click` / `fill`, then re-check logs.

Prefer element IDs as selectors — they're stable. Avoid long attribute selectors.
Sample system prompt

For one-off sessions with any MCP client:

You have access to the Feedthrough MCP server. It gives you live access to a running web app
from inside the browser — console logs, network requests, DOM state, and the ability to click
and fill inputs. Start by calling get_instructions() for the recommended workflow.

Security

v1 is local-only. Two guards enforce this:

  • Localhost binding — the WebSocket server binds to 127.0.0.1, so it is not reachable from other machines on the network.
  • Origin validation — each incoming WebSocket connection is checked against its Origin header. Loopback origins (localhost, 127.0.0.1, ::1) are always accepted, as is any host ending with an allowed suffix (default .test, so local dev domains like Laravel Valet's myapp.test connect out of the box). Override the suffix list with FEEDTHROUGH_ALLOWED_HOST_SUFFIXES (comma-separated; replaces the default — set it empty for loopback-only). Any other origin is rejected. A .test origin can only be presented by a page actually served from a .test host, which resolves locally, so this widens which local origins connect, not network reach.
What gets captured

Captured network requests include request and response bodies and headers, including Authorization, Cookie, and any other headers your app sends. That's intentional — debugging auth and session flows needs them. But the data does leave the page over the local WebSocket, flows through the MCP server, and reaches whichever AI agent you've connected. If that agent is cloud-backed, sensitive values reach the provider. Run Feedthrough only on dev machines and dev data. Do not inject @feedthrough/core into production builds.


Development

pnpm install       # install all workspace deps
pnpm build         # build all packages
pnpm typecheck     # typecheck all packages

Requires Node.js ≥ 22 and pnpm.

Releasing

Packages are versioned independently — bump only the package(s) you actually changed and leave the rest alone. Publishing to npm is handled by CI: the Publish to npm workflow runs on every published GitHub Release and publishes only the packages whose name@version isn't on npm yet, skipping the ones already published (via OIDC trusted publishing, no tokens).

To cut a release:

# 1. Bump the changed package(s) only
pnpm --filter @feedthrough/mcp exec npm version 0.1.1 --no-git-tag-version
# When bumping @feedthrough/mcp, also bump the version (and packages[].version) in
# packages/mcp/server.json to match — the MCP registry validates them against npm.
git add packages/mcp/package.json packages/mcp/server.json
git commit -m "Release @feedthrough/mcp 0.1.1"
git push

# 2. Create a GitHub Release (this triggers the publish workflow)
gh release create v0.1.1 --title "v0.1.1" --notes "..."

The workflow builds all packages and publishes only the newly bumped ones. It also publishes @feedthrough/mcp to the official MCP registry (io.github.feedthrough/feedthrough) via GitHub OIDC whenever the registry is missing the current version, so a failed registry publish can be retried by re-running the workflow (Actions tab, "Run workflow") with no version bump. Mark a release as a pre-release to skip publishing.


License

MIT — see LICENSE.

1# Feedthrough
2 
3**Debug with AI — from inside your app.**
4 
5Feedthrough injects a lightweight debug bridge into any running web page, then exposes everything
6— DOM state, console logs, network requests, and user interactions — as MCP tools. Any
7MCP-compatible AI agent can inspect and drive the page conversationally, in real time.
8 
9```
10Browser (any)
11 └── @feedthrough/core ← injected into your page
12 ├── console interceptor
13 ├── fetch / XHR interceptor
14 └── DOM inspector
15 ↕ WebSocket
16@feedthrough/mcp ← MCP server, exposes tools over stdio
17 └── Tools: click, fill, inspect_element, query_dom,
18 get_console_logs, get_network_requests, …
19 ↕ MCP protocol
20Claude Code / Cursor / any MCP client
21```
22 
23---
24 
25## The name
26 
27Many physics and chemistry experiments run inside a sealed vacuum chamber, with all the air
28pumped out so nothing contaminates the experiment. The catch: you still need to control
29instruments inside the chamber and read their measurements, and the smallest air leak ruins
30the run. A feedthrough is the part that solves this — a specially engineered connector that
31carries electrical signals through the chamber wall while keeping the vacuum perfectly intact.
32You can't reach inside, but the feedthrough lets you observe and control what's happening in
33there anyway.
34 
35The parallel is exact: Feedthrough extracts runtime debug data from inside a running web app
36without disturbing it, and sends control signals back in — clicks, keystrokes, DOM queries —
37without breaking the execution environment.
38 
39---
40 
41## Why Feedthrough?
42 
43Every other browser MCP tool is an **external observer** — it controls the browser from outside
44via Puppeteer or CDP and only works in Chrome. Feedthrough is an **embedded agent**. It runs
45*inside* the page, so it sees:
46 
47- Framework internals (React component trees, Redux store, custom globals)
48- Any browser, not just Chrome
49- Your existing dev workflow — no separate controlled browser to launch
50- Cypress's own browser context during test runs
51 
52---
53 
54## Packages
55 
56| Package | Description |
57|---|---|
58| [`@feedthrough/core`](packages/core) | In-browser bridge — intercepts console, fetch, XHR; handles commands |
59| [`@feedthrough/mcp`](packages/mcp) | MCP server — bridges any MCP client to the browser via WebSocket |
60| [`@feedthrough/cypress`](packages/cypress) | Cypress adapter — auto-injects the bridge before each test page load |
61| [`@feedthrough/playwright`](packages/playwright) | Playwright adapter — injects the bridge via `page.addInitScript()` |
62| [`@feedthrough/vite`](packages/vite) | Vite plugin for apps with a static `index.html` |
63| [`@feedthrough/webpack`](packages/webpack) | Webpack plugin — adds bridge as a global entry point |
64| [`@feedthrough/nextjs`](packages/nextjs) | Next.js adapter — wraps `next.config.ts` with `withFeedthrough()` |
65| [`@feedthrough/nuxt`](packages/nuxt) | Nuxt 3 module |
66| [`@feedthrough/sveltekit`](packages/sveltekit) | SvelteKit adapter — injects via the `handle` hook |
67| [`@feedthrough/remix`](packages/remix) | Remix adapter — injects via a Vite dev server middleware |
68 
69---
70 
71## Framework support
72 
73| Framework | Adapter | Notes |
74|---|---|---|
75| Vite + React / Vue / Solid / Preact | `@feedthrough/vite` | Static `index.html` — plugin uses `transformIndexHtml` |
76| Next.js | `@feedthrough/nextjs` | Wraps the webpack config; dev only |
77| Nuxt 3 | `@feedthrough/nuxt` | Registers as a Nuxt module; dev only |
78| SvelteKit | `@feedthrough/sveltekit` | `handle` hook with `transformPageChunk`; dev only |
79| Remix | `@feedthrough/remix` | Vite dev server middleware; dev only |
80| Webpack apps | `@feedthrough/webpack` | Global entry point; guards against production mode |
81| Cypress | `@feedthrough/cypress` | `window:before:load` hook |
82| Playwright | `@feedthrough/playwright` | `page.addInitScript()` |
83 
84---
85 
86## Quick start
87 
88### 1. Start the MCP server
89 
90```bash
91npx @feedthrough/mcp
92```
93 
94The server listens for browser connections on `ws://127.0.0.1:8765` and exposes MCP tools on
95stdio. Override the port with `FEEDTHROUGH_PORT=9000`. If the port is already taken, the server
96steps up to the next free one rather than refusing to start — see
97[Running several sessions at once](#running-several-sessions-at-once).
98 
99### 2. Add it to your MCP client config
100 
101```json
102{
103 "mcpServers": {
104 "feedthrough": {
105 "command": "npx",
106 "args": ["@feedthrough/mcp"]
107 }
108 }
109}
110```
111 
112### 3. Inject the bridge into your page
113 
114**Vite + React / Vue / Solid / Preact:**
115 
116```ts
117// vite.config.ts
118import { feedthrough } from "@feedthrough/vite";
119export default defineConfig({ plugins: [feedthrough()] });
120```
121 
122**Next.js:**
123 
124```ts
125// next.config.ts
126import { withFeedthrough } from "@feedthrough/nextjs";
127export default withFeedthrough()({ /* your next config */ });
128```
129 
130**Nuxt 3:**
131 
132```ts
133// nuxt.config.ts
134export default defineNuxtConfig({ modules: ["@feedthrough/nuxt"] });
135```
136 
137**SvelteKit:**
138 
139```ts
140// src/hooks.server.ts
141import { feedthroughHandle } from "@feedthrough/sveltekit";
142import { sequence } from "@sveltejs/kit/hooks";
143export const handle = sequence(feedthroughHandle);
144```
145 
146**Remix:**
147 
148```ts
149// vite.config.ts
150import { feedthrough } from "@feedthrough/remix";
151export default defineConfig({ plugins: [remix(), feedthrough()] });
152```
153 
154**Webpack:**
155 
156```ts
157// webpack.config.mjs
158import { FeedthroughPlugin } from "@feedthrough/webpack";
159export default { plugins: [new FeedthroughPlugin()] };
160```
161 
162**Cypress:**
163 
164```ts
165// cypress/support/e2e.ts
166import { setupFeedthrough } from "@feedthrough/cypress";
167setupFeedthrough();
168```
169 
170**Playwright:**
171 
172```ts
173// import test from the adapter instead of @playwright/test
174import { test, expect } from "@feedthrough/playwright";
175```
176 
177**Or manually (any bundler):**
178 
179```ts
180// main.ts
181if (import.meta.env.DEV) {
182 import("@feedthrough/core").then(({ init }) => init());
183}
184```
185 
186### 4. Open your page and start asking
187 
188Once the bridge connects you'll see `[feedthrough] tab connected` in the MCP server output.
189For the simplest experience, keep a single tab open. Multiple tabs can connect at the same time
190and commands are routed to the most recently active one, but a single tab avoids any ambiguity.
191 
192Then ask your AI agent:
193 
194```
195> What's on the page right now?
196> Click the submit button and tell me what network requests fired
197> Why is the counter showing the wrong value?
198```
199 
200---
201 
202## MCP tools
203 
204| Tool | Description |
205|---|---|
206| `get_instructions()` | Usage guide — recommended workflow, tool ordering, and selector tips |
207| `query_dom(selector)` | All elements matching a CSS selector |
208| `inspect_element(selector, properties?)` | Tag, attributes, full bounding rect + inViewport, ancestor `path`, curated computed styles, overflow info (clipped/overflowing content), `clipped`-by-ancestor info, effective visibility (`visible` + `hiddenReason`, accounting for ancestors), occlusion (`hittable` + `occludedBy`), accessibility (`a11y`: role, name, states), `pseudo` ::before/::after content, live form state; `properties` reads extra CSS props by name |
209| `get_html(selector)` | Raw outerHTML of a region (capped at 50 KB) |
210| `get_console_logs(limit?, levels?, match?, since?)` | Console output (all methods) plus uncaught errors & promise rejections; filter by `levels`, `match`, or `since` timestamp |
211| `get_network_requests(filter?, since?)` | Captured fetch + XHR — URL, method, status, duration, headers, request/response bodies (10 KB cap); narrow by `filter` or `since` |
212| `get_page_info()` | URL, title, readyState, viewport size, scroll position, user agent, and which bridge this page is connected to |
213| `connection_status()` | Connected tabs and which one is active, plus this server's name, version, and bound port |
214| `click(selector)` | Click an element via native `click()` (fires click + default activation, not the pointer sequence) |
215| `fill(selector, value)` | Set an input/textarea/select value (fires input + change, not keystrokes) |
216| `hover(selector)` | Fire mouseover/mouseenter to mount hover UI (JS handlers, not CSS `:hover`) |
217| `press_key(selector, key)` | Dispatch a key press — Enter, Escape, Tab, arrow keys, or a character |
218| `set_style(selector, properties)` | Preview a visual fix — set inline CSS live (not saved to source) |
219| `set_attribute(selector, name, value)` | Preview an attribute change — toggle disabled, swap a class, set aria-* (`null` removes) |
220| `set_text(selector, text)` | Preview wording/label changes — replace an element's text |
221| `reset_overrides()` | Undo every live `set_style` / `set_attribute` / `set_text` change |
222 
223**Live edit is a preview, not a save.** `set_style` / `set_attribute` / `set_text` mutate the
224running DOM so the agent can show you a fix without a rebuild. They are *not* written to your
225source and reset on reload/HMR. The loop: the agent previews live, you confirm, then it edits the
226actual source to make it stick. Changes a framework owns (text, controlled attributes) may be
227overwritten on the next render — the tool result flags this so the agent can tell you.
228 
229---
230 
231## Example app
232 
233`examples/react-app` is a small React app with three deliberate bugs — a good sandbox for
234trying out the diagnostic workflow:
235 
236```bash
237# Terminal 1 — app
238cd examples/react-app && pnpm dev # http://localhost:5173
239 
240# Terminal 2 — MCP server
241cd packages/mcp && node dist/index.js
242```
243 
244Connect an AI agent and ask it to find what's wrong. The three bugs are all invisible from the
245UI but findable in under a minute via `get_console_logs`, `get_network_requests`, and `query_dom`.
246 
247---
248 
249## Running several sessions at once
250 
251Two AI agent sessions on one machine each start their own Feedthrough MCP server, and only one
252of them can have port 8765. That is handled, but it is worth knowing how.
253 
254**The server moves, and tells you where it went.** On a busy port it steps up (8766, 8767, …)
255instead of failing. `connection_status()` reports the port it actually bound, and the agent
256passes that to the dev server it starts:
257 
258```bash
259FEEDTHROUGH_PORT=8766 npm run dev
260```
261 
262Every build-tool adapter — vite, webpack, nextjs, nuxt, sveltekit, remix — reads `FEEDTHROUGH_PORT`
263(or `FEEDTHROUGH_URL` for a full `ws://` URL) in Node at config-load time and bakes the result into
264the injected bridge. So a committed, argument-free `feedthrough()` pairs correctly in any session,
265with no file edits. An explicit `serverUrl` option always wins over the environment.
266 
267**If you start the dev server yourself**, the environment is unset and the page falls back to
2688765, which may be another session's server. Either export the port before starting it, or pin
269one per project (below).
270 
271**Pinning a port per project.** For a project you always work on in its own session, pin the port
272on both ends and neither has to think about it. In `.mcp.json`:
273 
274```json
275{
276 "mcpServers": {
277 "feedthrough": {
278 "command": "npx",
279 "args": ["@feedthrough/mcp"],
280 "env": { "FEEDTHROUGH_PORT": "8770" }
281 }
282 }
283}
284```
285 
286and in the app's config, `feedthrough({ serverUrl: "ws://localhost:8770" })`.
287 
288**Telling bridges apart.** Each server picks a readable name at startup (`quiet-olive-heron`) and
289sends it to every page that connects. The page logs one line to the browser console, stores it on
290`window.__feedthrough.server`, and returns it from `get_page_info()`. The agent's own name comes
291from `connection_status()`. Two different names mean the tab is paired with another session's
292server — so "am I driving the right app?" is one call, not something you notice by watching the
293wrong window change.
294 
295---
296 
297## Using with an AI agent
298 
299### Recommended workflow
300 
3011. `connection_status()` — confirm the bridge is connected before anything else
3022. `get_console_logs()` — errors and app output often identify the root cause immediately
3033. `get_network_requests()` — look for failed fetches, wrong URLs, or missing calls
3044. `query_dom(selector)` — find elements and check what's rendered
3055. `inspect_element(selector)` — deep-dive on a specific element
3066. `click()` / `fill()` — interact, then re-check logs and network
307 
308### Project-memory snippet
309 
310Add this to whatever project-memory file your AI agent reads — `CLAUDE.md` for Claude Code,
311`.cursor/rules/*.md` for Cursor, and so on — to prime it with the right workflow:
312 
313```markdown
314## Debugging with Feedthrough
315 
316A Feedthrough MCP server is configured. When investigating UI bugs:
317 
3181. Call `connection_status()` first — fail fast if no browser is connected.
3192. Check `get_console_logs()` before touching the DOM.
3203. Check `get_network_requests()` for failed or missing API calls.
3214. Use `query_dom` to orient yourself, `inspect_element` to dig into a specific element.
3225. Interact with `click` / `fill`, then re-check logs.
323 
324Prefer element IDs as selectors — they're stable. Avoid long attribute selectors.
325```
326 
327### Sample system prompt
328 
329For one-off sessions with any MCP client:
330 
331```
332You have access to the Feedthrough MCP server. It gives you live access to a running web app
333from inside the browser — console logs, network requests, DOM state, and the ability to click
334and fill inputs. Start by calling get_instructions() for the recommended workflow.
335```
336 
337---
338 
339## Security
340 
341v1 is local-only. Two guards enforce this:
342 
343- **Localhost binding** — the WebSocket server binds to `127.0.0.1`, so it is not reachable
344 from other machines on the network.
345- **Origin validation** — each incoming WebSocket connection is checked against its `Origin` header.
346 Loopback origins (`localhost`, `127.0.0.1`, `::1`) are always accepted, as is any host ending
347 with an allowed suffix (default `.test`, so local dev domains like Laravel Valet's `myapp.test`
348 connect out of the box). Override the suffix list with `FEEDTHROUGH_ALLOWED_HOST_SUFFIXES`
349 (comma-separated; replaces the default — set it empty for loopback-only). Any other origin is
350 rejected. A `.test` origin can only be presented by a page actually served from a `.test` host,
351 which resolves locally, so this widens *which local origins* connect, not network reach.
352 
353### What gets captured
354 
355Captured network requests include request and response **bodies and headers**, including
356`Authorization`, `Cookie`, and any other headers your app sends. That's intentional — debugging
357auth and session flows needs them. But the data does leave the page over the local WebSocket,
358flows through the MCP server, and reaches whichever AI agent you've connected. If that agent is
359cloud-backed, sensitive values reach the provider. Run Feedthrough only on dev machines and dev
360data. Do not inject `@feedthrough/core` into production builds.
361 
362---
363 
364## Development
365 
366```bash
367pnpm install # install all workspace deps
368pnpm build # build all packages
369pnpm typecheck # typecheck all packages
370```
371 
372Requires Node.js ≥ 22 and pnpm.
373 
374## Releasing
375 
376Packages are versioned **independently** — bump only the package(s) you actually changed and leave
377the rest alone. Publishing to npm is handled by CI: the `Publish to npm` workflow runs on every
378published GitHub Release and publishes only the packages whose `name@version` isn't on npm yet,
379skipping the ones already published (via OIDC trusted publishing, no tokens).
380 
381To cut a release:
382 
383```bash
384# 1. Bump the changed package(s) only
385pnpm --filter @feedthrough/mcp exec npm version 0.1.1 --no-git-tag-version
386# When bumping @feedthrough/mcp, also bump the version (and packages[].version) in
387# packages/mcp/server.json to match — the MCP registry validates them against npm.
388git add packages/mcp/package.json packages/mcp/server.json
389git commit -m "Release @feedthrough/mcp 0.1.1"
390git push
391 
392# 2. Create a GitHub Release (this triggers the publish workflow)
393gh release create v0.1.1 --title "v0.1.1" --notes "..."
394```
395 
396The workflow builds all packages and publishes only the newly bumped ones. It also publishes
397`@feedthrough/mcp` to the [official MCP registry](https://registry.modelcontextprotocol.io)
398(`io.github.feedthrough/feedthrough`) via GitHub OIDC whenever the registry is missing the current
399version, so a failed registry publish can be retried by re-running the workflow (Actions tab,
400"Run workflow") with no version bump. Mark a release as a pre-release to skip publishing.
401 
402---
403 
404## License
405 
406MIT — see [LICENSE](LICENSE).
407 
View all files on GitHub →

Details

Updated
Sep 4, 2026
License
MIT
Stars on the repo
★ 4
Checked
Source
feedthrough/feedthrough ↗
Creator
feedthrough · 1 listing
View creator

Similar MCP servers

See all →
SkillTests in real browsers via Chrome DevTools MCPBrowser testing with devtoolsaddyosmani/agent-skills · by addyosmaniTests in real browsers via Chrome DevTools MCP. Use when building or debugging anything that runs in a browser. Use when you need to inspect the DOM, capture console errors, analyze network requests, profile performance, or verify visual output with real runtime data. Requires the chrome-devtools MCP server to be configured.★ 98.7k GitHub starsAgentThis MCP server attempts to exercise all the featuresEverything MCP Servermodelcontextprotocol/servers · by modelcontextprotocolThis MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test…★ 90.5k GitHub starsAgentA Model Context Protocol server that provides web contentFetch MCP Servermodelcontextprotocol/servers · by modelcontextprotocolA Model Context Protocol server that provides web content fetching capabilities. This server enables LLMs to retrieve and process content…★ 90.5k GitHub starsAgentNode.js server implementing Model Context ProtocolFilesystem MCP Servermodelcontextprotocol/servers · by modelcontextprotocolNode.js server implementing Model Context Protocol (MCP) for filesystem operations.★ 90.5k GitHub starsSkillAbout libraries, frameworks, API referencesContext7 MCPupstash/context7 · by upstashThis skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.★ 62.3k GitHub starsSkillUses Chrome DevTools via MCP for efficient debuggingChromeDevTools/chrome-devtools-mcp · by ChromeDevToolsUses Chrome DevTools via MCP for efficient debugging, troubleshooting and browser automation. Use when debugging web pages, automating browser interactions, analyzing performance, or inspecting network requests. This skill does not apply to `--slim` mode (MCP configuration).★ 52.4k GitHub stars