@tscafejr/mcp
This MCP server provides browser automation for a running web app, letting you screenshot pages, interact with elements, and diagnose frontend errors.
screenshot_page: Navigate to a route and capture a screenshot, with optional viewport/emulation (mobile, PWA/safe-area), custom base URL or port, and wait time.type_into_element: Navigate to a route, type text into a CSS selector, optionally wait for streaming content, and return a screenshot.inspect_network_errors: Capture console logs/warnings, uncaught JS exceptions, and 4xx/5xx network failures for a route, optionally including console logs.
Provides Puppeteer-driven screenshots, typing, and network/console diagnostics for web applications deployed on Netlify (including dev and previews).
Provides Puppeteer-driven screenshots, typing, and network/console diagnostics for web applications built with Next.js.
Provides Puppeteer-driven screenshots, typing, and network/console diagnostics for web applications built with Vite.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@tscafejr/mcptake a screenshot of the homepage"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@tscafejr/mcp
MCP servers I use across projects distributed as a single npm package with one bin per server.
Installation (consumers)
Wire up one bin per project, not the whole package — a project gets only the
servers it actually needs. The invocation is npx -y <package> <bin>:
// .mcp.json in the project that needs a browser
{
"mcpServers": {
"visualizer": {
"command": "npx",
"args": ["-y", "@tscafejr/mcp", "mcp-visualizer"],
"env": { "MCP_DEV_SERVER_PORT": "5173" }
}
}
}// .mcp.json in the project that needs a database
{
"mcpServers": {
"db": {
"command": "npx",
"args": ["-y", "@tscafejr/mcp", "mcp-db"],
"env": { "MCP_DB_URL": "sqlite:./data/app.db" }
}
}
}npx <package> <name> works because the package ships a bin called mcp —
npm derives the command from the unscoped package name, so a multi-bin package
without one fails with "could not determine executable to run". That mcp bin
is a dispatcher: it takes the server name and hands off. The explicit
npx -y -p @tscafejr/mcp mcp-db form works too and does not depend on it.
Both bins ship in one package, so npx installs all of its dependencies
regardless of which bin you run — including puppeteer, which downloads Chromium.
In a project that only wants mcp-db or mcp-logs, add
"PUPPETEER_SKIP_DOWNLOAD": "1" to that server's env to skip it.
Related MCP server: DevServer MCP
Available servers
Bin | Source | Description |
|
| Drives a real browser against a running web app: navigate, inspect, click, type, screenshot, diagnose and visually diff. Framework-agnostic — Vite, Next.js, CRA, Netlify dev, deploy previews, anything that serves HTTP. |
|
| Read-only SQL against SQLite or Postgres: schema introspection, queries, query plans, and migration drift. Writes are impossible by construction. |
|
| Reads what your running processes are printing: captures a command's output, or tails log files you already have. Errors deduplicated, stack traces intact, ANSI stripped. |
|
| Dispatcher, not a server. Exists so |
Requirements: Node 18+ generally; mcp-db's SQLite support needs Node 22.5+
for the built-in node:sqlite module.
mcp-visualizer
How it works
One Chromium instance stays alive across tool calls, so cookies, localStorage,
scroll position and emulation settings persist. You can log in once and keep
working, and you only pay browser startup on the first call. The session closes
itself after five idle minutes, or immediately on browser_close.
Two things make the tools cheap to use:
browser_snapshotbefore you interact. It returns a text outline of the page — controls, headings, landmarks — each tagged with a[ref=eN]. Pass that ref tobrowser_click/browser_typeinstead of guessing a CSS selector from a screenshot. It costs a fraction of an image.Screenshots are capped. Output is downscaled to
max_width(default 1000px) and viewport-only unless you ask forfull_page. Targeting aselectorcaptures just that element, which is usually all you need.
Console errors, uncaught exceptions and 4xx/5xx responses are recorded continuously and appended to every tool result, so a screenshot of a blank page tells you why it is blank.
Target resolution
Highest precedence to lowest:
Per-call
url— absolute, wins outright.Per-call
base_url— e.g.https://preview-123.netlify.app.Per-call
port— localhost shorthand.MCP_DEV_SERVER_URLenv — full base URL.MCP_DEV_SERVER_HOST+MCP_DEV_SERVER_PORTenv.http://localhost:3000.
A call with no target at all acts on the page already open.
Environment
Variable | Default | Purpose |
| — | Full base URL. |
|
| Host used with |
| — | Port on that host. |
| — | JSON object of extra request headers (preview bypass tokens). |
| — |
|
|
| Idle time before the browser closes itself. |
|
| Default desktop viewport. |
|
| Default screenshot width cap. |
|
| Default cap for |
|
| Where |
| — |
|
|
|
|
| — |
|
Example client configs:
// Vite project
{ "mcpServers": { "visualizer": {
"command": "npx", "args": ["-y", "@tscafejr/mcp", "mcp-visualizer"],
"env": { "MCP_DEV_SERVER_PORT": "5173" }
}}}
// Netlify dev
{ "mcpServers": { "visualizer": {
"command": "npx", "args": ["-y", "@tscafejr/mcp", "mcp-visualizer"],
"env": { "MCP_DEV_SERVER_PORT": "8888" }
}}}
// Password-protected deploy preview
{ "mcpServers": { "visualizer": {
"command": "npx", "args": ["-y", "@tscafejr/mcp", "mcp-visualizer"],
"env": {
"MCP_DEV_SERVER_URL": "https://preview-123.example.com",
"MCP_DEV_SERVER_BASIC_AUTH": "preview:hunter2"
}
}}}Tools
Look
Tool | Notes |
| Open a route. Also carries emulation: |
| Text outline with |
| Viewport by default. |
| The same page at several widths in one call (default 375 / 768 / 1280). |
| Compare against a saved baseline; reports changed pixel count, percentage, bounding box and a diff image. |
Act
Tool | Notes |
|
|
|
|
| Keys and chords — |
|
|
| Choose |
Every action tool takes ref / selector / find_text to target an element,
wait_for (a selector, or text=Some copy) to wait afterwards, and optional
screenshot / snapshot flags to return the result.
Diagnose
Tool | Notes |
| Run JS in the page and get JSON back. Assert app state without spending a screenshot. |
| Console errors/warnings, exceptions, failed and 4xx/5xx requests. |
| Drop the session — cookies, storage and emulation with it. |
screenshot_page, type_into_element and inspect_network_errors still work as
one-shot wrappers over the same engine. Set MCP_VISUALIZER_LEGACY_TOOLS=0 to
hide them.
PWA and safe areas
pwa: true emulates an iOS standalone install: display-mode: standalone,
navigator.standalone, an iPhone viewport, and real env(safe-area-inset-*)
values via Chrome DevTools Protocol — your own layout responds to them, no
class-name assumptions. Override the numbers with safe_area: { top, bottom },
and turn off the tinted guide bars with pwa_overlay: false.
Visual baselines
browser_diff stores PNGs in .visualizer-baselines/ under the working
directory. Commit them if you want regressions caught across machines; ignore
the directory if you only use it locally within a session.
The comparison is pixel-exact, so a baseline is only meaningful against the same
capture settings — keep max_width, full_page and viewport identical between
runs, or re-record with update: true.
mcp-db
Read-only access to a project's database, so schema questions get answered from the database rather than guessed from the code. SQLite and Postgres.
It cannot write
Two independent layers, both verified:
The connection is read-only. SQLite is opened with
readOnly: true; every Postgres statement runs inside aBEGIN READ ONLYtransaction that is rolled back afterwards.DELETE,UPDATE,CREATEandDROPall fail at the engine — "cannot execute DELETE in a read-only transaction".A statement gate in front of it. Only
SELECT,WITH,EXPLAIN,SHOW,TABLEandVALUESare accepted, chained statements are refused, and a data-modifying CTE —WITH x AS (DELETE ... RETURNING ...), which legitimately starts withWITH— is caught by keyword scan after comments and string literals are stripped.
The gate exists for clear error messages; the engine is the actual guarantee.
Configuration
Variable | Default | Purpose |
| (required) |
|
| auto-detected | Overrides migration directory discovery. |
|
| Default row cap for |
|
| Output cap per result. |
|
| Per-cell truncation width. |
|
| Postgres |
|
| SQLite |
MCP_DB_URL accepts the sqlite:data/app.db?mode=rwc form sqlx uses — the
query string is ignored and relative paths resolve against the server's working
directory. A leading ~ expands to your home directory, so an absolute path
need not be hardcoded. $VAR is deliberately not expanded: a Postgres
password may legitimately contain $, and expanding it would corrupt real
connection strings. If your MCP client supports ${VAR} in its own config
(Claude Code does), use that instead of putting a password in the file.
The server prints what it resolved to stderr as soon as it starts, so a bad
path shows up at launch rather than on the first query. Migration directories are discovered in this order: migrations/,
supabase/migrations/, db/migrations/, drizzle/, prisma/migrations/.
Managed Postgres (Supabase, Neon, RDS) terminates TLS with a chain Node does not
trust by default, so non-localhost connections use rejectUnauthorized: false.
Pointing this at a database your app is actively writing to is fine. The
connection is read-only and holds no transaction between calls; SQLite gets a
busy_timeout so a concurrent writer produces a short wait rather than a
SQLITE_BUSY error. Under WAL, readers and writers do not block each other at
all.
Tools
Tool | Notes |
| No args: every table and view with row counts. |
| A single read-only statement. Results are capped by wrapping the query, and one extra row is fetched so "exactly 3 rows" is distinguishable from "the first 3 of many". |
| Query plan. |
| How tables connect. No args: every relationship. |
| Postgres row-level security — which tables have RLS on, and each policy's command, roles and |
| Migration files on disk versus what the database applied. Reports pending migrations, ones applied but missing from disk (you switched branches), and failures. Understands sqlx, Supabase, Drizzle and plain |
SQLite support uses Node's built-in node:sqlite, so it adds no dependency, but
it needs Node 22.5 or newer. Postgres uses pg, imported lazily so a
SQLite-only project never loads it.
Relationships without foreign keys
db_relations reads declared foreign keys, and then fills the gaps by matching
the <table>_id column convention — player_stat.player_id is reported as
pointing at player even with no REFERENCES clause. Singular and plural forms
both resolve, so user_id finds users. Every link is labelled fk or
inferred; inferred links are a guess from a name, so confirm one before
depending on it.
This is what makes the tool useful on schemas that lean on convention rather than constraints. Join paths prefer declared keys and fall back to inferred links only when no declared route exists:
public.activity → public.teams in 2 hops
activity.link_id → links.id [inferred]
links.team_id → teams.id [fk]
SELECT *
FROM public.activity a
JOIN public.links l ON a.link_id = l.id
JOIN public.teams t ON l.team_id = t.idRow-level security
db_policies exists for the failure mode where a query works for you and
returns nothing for a real user. It flags both silent states:
RLS enabled with no policies — every row is denied to non-owner roles, including
anonandauthenticated. Queries return empty rather than erroring, so this looks like missing data.RLS off — no row filtering at all; any role with table privileges reads every row.
table rls policies note
public.activity OFF 0 unfiltered
public.api_keys enabled 0 DENIES ALL
public.links enabled 2mcp-logs
How it works
Two ways in, one set of tools across both.
Capture a process. Put mcp-logs run -- in front of whatever you already run.
It behaves exactly as before — output still streams to your terminal, colours
intact, exit code preserved, Ctrl-C still reaches the child — while a copy lands
in .mcp-logs/<name>.ndjson.
mcp-logs run -- npm run dev # stream named "dev", from the script
mcp-logs run --name backend -- npm start
mcp-logs run --name mobile -- npx expo startRead files you already have. Point MCP_LOGS_FILES at globs and they show up
as sources with no change to how anything is started.
The server itself owns no processes and holds no state but read cursors — it only ever reads files. That is what lets it survive a client restart, and read a dev server you started in a terminal long before the MCP client existed.
Four things keep the output cheap enough to read on every turn:
logs_taildefaults to what is new. It remembers where you last read, so the loop is: trigger the behaviour, calllogs_tail, get only what that produced. Passsince: "start"to re-read recent history instead.Stack traces stay whole. Indented frames,
at ...,Caused by:andFile "..."lines are folded into the entry that raised them, so a trace is one entry rather than thirty — and a trace can promote its own entry toerrorwhen its first line never said so.Duplicates collapse.
logs_errorskeys on the message, not the rendered line, so the same crash on six requests a second apart comes back once as(x6)rather than six identical traces.Noise is stripped. ANSI colour codes go, a leading ISO timestamp is dropped in favour of the rendered time column, and output is capped from the front so the newest lines are the ones that survive.
It does not push
MCP gives a server no way to put anything into an agent's context. The protocol
has notifications/message and resources/updated, but clients do not inject
either — resources are user-initiated pulls. So these tools are pull-only by
design, and logs_tail's cursor is what makes that cheap: asking again after an
action costs only the output that action produced.
Configuration
Nothing is required. With no configuration at all the server watches
./.mcp-logs, which is empty until the first mcp-logs run.
Variable | Default | Purpose |
|
| Where |
| — | Comma-separated globs of existing log files to expose as sources. |
|
| Default entries returned by |
|
| Output cap per tool result. |
|
| How far back a tail read seeks. |
|
| How far back |
|
| Size a captured stream reaches before rotating. |
|
| Treat stderr with no keyword of its own as a warning. |
// captures only — start your dev server with `mcp-logs run`
{ "mcpServers": { "logs": {
"command": "npx", "args": ["-y", "@tscafejr/mcp", "mcp-logs"],
"env": { "PUPPETEER_SKIP_DOWNLOAD": "1" }
} } }// captures plus log files that already exist
{ "mcpServers": { "logs": {
"command": "npx", "args": ["-y", "@tscafejr/mcp", "mcp-logs"],
"env": {
"MCP_LOGS_FILES": "logs/*.log,supabase/.temp/*.log",
"PUPPETEER_SKIP_DOWNLOAD": "1"
}
} } }Add .mcp-logs/ to the consuming project's .gitignore.
Tools
Tool | What it answers |
| What is being captured, how big it is, when it last wrote, and how many errors are in recent history. Start here — its names are the |
| What has been printed since I last looked. Filters on |
| What is broken, across every source, deduplicated with counts. |
| Where did this appear — a regex over history with surrounding lines, |
Levels
There is no level field to trust, so severity is inferred from the text: named
classes (TypeError, NullPointerException) and words like fatal, panic,
failed read as errors; warn and deprecated as warnings. stderr on its own
is a hint rather than proof — plenty of tools write ordinary progress there — so
it only lifts an otherwise unremarkable line to warn. Set
MCP_LOGS_STDERR_WARN=0 if a noisy process makes even that too much.
Rotation
A captured stream rotates to <name>.ndjson.1 at MCP_LOGS_MAX_BYTES and one
previous generation is kept, so a stream costs at most twice that on disk.
logs_search reaches into the rotated generation; anything older is gone.
Troubleshooting
npm error could not determine executable to run
npm picks the bin for npx <package> <args> by stripping the scope off the
package name — @tscafejr/mcp becomes mcp — and looking for a bin with that
name. It falls back to the only bin when a package has exactly one. This package
had one bin through 0.4.0, so the short form worked; adding a second bin in 0.5.0
broke it for both servers.
On 0.5.1 or later: nothing to do. The
mcpdispatcher bin makes the short form resolve again.On 0.5.0: use the explicit package flag —
"args": ["-y", "-p", "@tscafejr/mcp", "mcp-visualizer"]. This form works on every version and never depends on bin-name inference.
Clearing the npx cache does not help; the resolution fails before the cache is consulted.
mcp-logs says there are no sources
Nothing captures itself. Either start a process through the collector —
mcp-logs run -- npm run dev, in your own terminal, not through the MCP client —
or set MCP_LOGS_FILES to globs of log files that already exist. Both are
resolved against the directory the MCP client launched the server from, which is
the project root in most clients; logs_sources prints the paths it settled on.
mcp-db reports a path you did not configure
It prints what it resolved at startup:
mcp-db: sqlite → /Users/you/project/data/app.dbRelative paths resolve against the working directory the MCP client launched the
server in, which is not always the project root. A leading ~ is expanded by
the server, so sqlite:~/code/project/data/app.db is portable across machines
and does not rely on the client expanding anything.
The database file does not exist yet
mcp-db will not create one. Run your app or your migration tool first — the
error names the absolute path it tried.
Adding a new server
Create
src/servers/<name>.ts. Start with a shebang so the built file is directly executable:#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // ...Keep the entry thin and put the implementation in
src/<name>/, the wayvisualizer.tsdelegates tosrc/visualizer/. Node ESM needs real extensions, so import local modules as./thing.js.Add one line to the
binmap inpackage.json:"bin": { "mcp": "dist/servers/mcp.js", "mcp-visualizer": "dist/servers/visualizer.js", "mcp-<name>": "dist/servers/<name>.js" }Register it in the dispatcher's
SERVERSmap insrc/servers/mcp.ts. Skipping this does not break thenpx -y -p <package> mcp-<name>form, butnpx <package> mcp-<name>will report an unknown server.The dispatcher splices its own argument out of
process.argvbefore handing off, so a server that takes arguments of its own — the waymcp-logs rundoes — sees them atargv[2]under either invocation form.Build and run locally:
npm run build npm run dev <name> # tsx, no build step npm run start <name> # runs the built dist/ output
That's it — chmod-bins.js reads package.json on every build and marks all bin outputs executable, so new entries pick up automatically.
Add the new row to the table above so consumers know what's available.
Formatting
Prettier, configured in .prettierrc.json — 100 columns, double quotes, trailing
commas, two-space indent.
npm run format # rewrite
npm run format:check # verify, non-zero exit if anything is unformattedMarkdown uses embeddedLanguageFormatting: "off" so the annotated JSON config
examples in this file survive — several carry // comments that are not valid
JSON and would otherwise fail to parse.
Releasing
The release script prompts for the bump type (major / minor / patch), runs npm version, builds, publishes, and pushes the commit + tag to your git remote.
npm run releaseEquivalent manual steps if you'd rather drive it yourself:
npm version patch # or: minor / major — bumps, commits, tags
npm publish # prepublishOnly rebuilds dist/
git push --follow-tags # if/when this dir has a git remoteAvailable Tools
3 toolsinspect_network_errorsC
Captures console logs (errors/warns), uncaught JS exceptions, and 4xx/5xx network failures.
| Name | Required | Description | Default |
|---|---|---|---|
| route | Yes | ||
| base_url | No | Full base URL override. See screenshot_page. | |
| port | No | Localhost port. Ignored if base_url is set. | |
| include_logs | No | Whether to include console logs/warnings |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description must carry the full burden. It does not disclose side effects, read-only nature, whether logs are cleared, or if the page is reloaded. The minimal description leaves behavioral traits ambiguous.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single concise sentence, front-loaded with the key action. It avoids unnecessary words, though it could expand slightly on usage without losing conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description should explain what the tool returns (e.g., list of errors). It fails to do so, leaving the agent without information on how to use the output. Behavioral and parameter gaps compound the incompleteness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75%, but the tool description adds no parameter meaning beyond the schema. The 'route' parameter remains undefined in both schema and description, requiring the agent to infer its purpose.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool captures console logs (errors/warns), uncaught JS exceptions, and 4xx/5xx network failures. This matches the tool name and distinguishes it from siblings like screenshot_page and type_into_element.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool vs alternatives (e.g., screenshot_page for visual state). The context signals indicate siblings, but the description does not leverage them.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
screenshot_pageB
Takes a screenshot of a running web app (local dev server, Netlify deploy preview, etc.).
| Name | Required | Description | Default |
|---|---|---|---|
| route | Yes | Path to visit (e.g. /ats) | |
| base_url | No | Full base URL override (e.g. http://localhost:8888 for Netlify dev, https://preview.example.com). Takes precedence over port and env vars. | |
| port | No | Port on localhost (e.g. 5173 for Vite, 3000 for Next/CRA, 8888 for Netlify dev). Ignored if base_url is set. | |
| mobile | No | ||
| pwa | No | Simulate iOS PWA/standalone experience: sets display-mode:standalone, emulates iPhone 15 Pro, and injects 34px bottom + 59px top safe area insets. | |
| wait_ms | No | Milliseconds to wait after page load before taking screenshot. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description only says 'takes a screenshot' without revealing behavioral details like side-effects (e.g., does it modify the app?), error handling, or authentication requirements. The description carries the full burden but adds almost no transparency beyond the basic action.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence that efficiently states the tool's function. It is concise without being overly sparse, though it could earn a 5 if it included a brief usage hint.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 6 parameters, no output schema, and no annotations, the description is too minimal. It does not explain the output format, prerequisites (e.g., app must be running), or how to handle errors. The agent lacks sufficient context to use the tool confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already covers 83% of parameters with descriptions. The description adds no additional parameter context beyond the tool's general purpose. Baseline 3 is appropriate since the schema does most of the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool takes a screenshot of a running web app, with specific examples like local dev server and Netlify deploy preview. This distinguishes it from sibling tools (inspect_network_errors, type_into_element) which have entirely different purposes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for taking screenshots of running web apps but provides no explicit guidance on when to use vs. not use, nor does it mention alternatives or exclusions. The context is clear but lacks direction for an AI agent comparing options.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
type_into_elementC
Navigates, types text into a selector, and returns a screenshot.
| Name | Required | Description | Default |
|---|---|---|---|
| route | Yes | ||
| selector | Yes | ||
| text | Yes | ||
| base_url | No | Full base URL override. See screenshot_page. | |
| port | No | Localhost port. Ignored if base_url is set. | |
| wait_ms | No | Milliseconds to wait after typing before taking screenshot. Useful for streaming responses (e.g. 10000 for 10s). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. It discloses basic actions but omits important behavior like waiting, error handling, or side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Single sentence efficiently conveys core functionality. Could be more detailed but remains focused.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Lacks information on return value structure, error conditions, and usage context for optional parameters. Insufficient for a 6-parameter tool without output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Adds some meaning to required params (route, selector, text) by framing them in an action, but does not explain optional params like base_url, port, wait_ms. Schema coverage is 50% and description partially compensates.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states it navigates, types text into a selector, and returns a screenshot. It distinguishes from sibling tools which handle network errors and standalone screenshots.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance on when to use this tool vs alternatives like screenshot_page or inspect_network_errors. Context is implied but not explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.3.0- First observed
inspect_network_errors - First observed
screenshot_page - First observed
type_into_element
TDQS
Scored across 3 tools
Each tool has a distinct primary purpose: inspecting errors, taking screenshots, and typing into elements. However, type_into_element also returns a screenshot, creating minor overlap with screenshot_page.
All names use lowercase snake_case with verb-like prefixes. 'inspect_network_errors' and 'type_into_element' are more descriptive, while 'screenshot_page' is shorter, but the pattern is consistent overall.
Three tools is on the low side for a web debugging/testing server. While the tools are focused, additional tools like navigation or clicking would make the set more comprehensive.
The server covers error inspection, screenshots, and typing, but lacks basic operations such as navigating to URLs, clicking elements, or retrieving page content. These omissions are notable for its stated purpose.
Maintenance
Related MCP Connectors
A MCP server built for developers enabling Git based project management with project and personal…
MCP server for understanding Javascript internals from ECMAScript specification.
MCP registry & directory: search, find & install 31k+ MCP servers & tools. Catalog and marketplace.
Publish and discover MCP servers via the official MCP Registry. Powered by HAPI MCP server.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that provides tools for interacting with Chrome through its DevTools Protocol, enabling remote control of Chrome tabs to execute JavaScript, capture screenshots, monitor network traffic, and more.29 npm53MIT
- AlicenseNot gradedqualityFmaintenanceAn MCP server that enables programmatic management and monitoring of development servers through a unified interface and interactive TUI. It provides tools for process control, log streaming, and experimental browser automation via Playwright.1MIT
- AlicenseBqualityCmaintenanceAn MCP server based on Puppeteer and Chrome DevTools Protocol for advanced browser debugging, performance analysis, and memory detection. It enables users to inspect DOM elements, monitor console errors, capture screenshots, and perform heap snapshot analysis through persistent browser connections.1015 npm1MIT
- FlicenseNot gradedqualityDmaintenanceAn MCP server that captures browser console logs and network requests via the Chrome DevTools Protocol. It allows users to monitor real-time logs, inspect network traffic, and execute JavaScript code directly in the browser context.-