webscope
It is a token-efficient MCP server for safely searching, surgically reading, and running declared read-only project scripts, with optional browser-test and write-script support when configured.
Search project text case-insensitively (literal or regex), scoped by folder/file, with context and concise/detailed grouped results.
Read only needed file chunks: symbols, Markdown sections, line ranges, log tails or matching lines, or a file index.
List and run manifest-declared read-only scripts by name/args, returning exit code and trimmed output; inspect script descriptions/schema without running.
Stay within the project root, skip secrets, avoid shell injection, and default to read-only behavior.
Per README/config, optionally drive Chrome for browser tests (
test) and run approved write scripts (run_write) when enabled.
Provides tools for analyzing and managing Laravel projects, including mapping frontend modules to backend endpoints, validating payloads against FormRequest rules, and cleaning test data.
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., "@webscopebusca 'getUser' en src y dame los archivos con más coincidencias"
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.
Webscope
A lightweight, token-efficient MCP server written in Go (stdio, one static binary, no runtime to install). It gives coding agents short, surgical answers: grouped search hits, one symbol instead of a whole file, tests that drive the user's open Chrome, and bounded project scripts.
Tool names, parameters, and messages are in English.
Scope. The core (search, read, run, run_write, test) knows no stack. Stack knowledge lives in packs: packs/laravel/ holds 23 adapters as scripts (Laravel, Vue, Astro, Python) that a manifest declares, and packs/laravel/stacks.json tells --init which of them a project gets. The test tool works on any app in Chrome; it was checked on a plain-ARIA page, a CRUD page with Vuetify look-alike markup and a CRUD page built with the real Vue 3 and Vuetify 3 libraries (fixtures/).
Status. v1.0.0 is the Go server: GitHub release with 5 binaries, 5 .mcpb bundles, checksums and server.json. The container image (ghcr.io/partprogramming09/webscope:1.0.0) is not pushed yet and the entry in the MCP registry is not published. The previous release 0.6.0 is the Python server (PyPI webscope-mcp; source in the legacy/python branch). The Laravel adapters came out of one application; the parity scripts (docs/parity-scripts/README.md) were also run live on a Laravel 12 fixture with two tenants on MySQL, PostgreSQL and SQLite (fixtures/laravel/run-engine-checks.sh), the other adapters only on that application, and most automated tests use toy file trees. The measurements below were taken with the earlier Python server and the same adapters; the Go server has its own measurements in docs/rework/.
Measured Effect
Why not the agent's own grep? Grep and read answer «where is X». They cannot compute an answer that crosses layers or reads the database in one step. Measured below: 5 calls against 1 for a column sheet, 6 against 1 for «who reads this column». Where plain search is enough (map of a module), there is no measurable difference: 6 calls against 7.
The table below was measured on the earlier Python server (0.6.0) on 2026-10-20, before the Go rewrite; the Go server has its own, smaller measurements in «Measured on the Go server» after it. Measured 2026-10-20 (second run of the same method): four read-only questions about a real Laravel + Vue monorepo, asked through claude -p (Sonnet) in a clean session, five runs each per condition (40 runs), order randomized per pair, one warm-up run per condition discarded, ground truth fixed by hand from sources that are not adapters (a grep of the code, direct SQL counts, a manual check of each file). Hypotheses and thresholds were written before the run: the column sheet and the consumers question cost at most half as much with webscope (met: 25 % and 19 % of the median cost); the module map differs by less than 20 % (met: 14 % lower). The method, the environment and the numbers per run are in bench/; the table below is printed by bench/report.py from bench/results/runs-v3.json.
Question | USD without: median (min–max) | USD with: median (min–max) | Calls without → with (median) | Complete answers without → with |
Backend module and tables of a frontend module | 0.050 (0.045–0.058) | 0.043 (0.037–0.050) | 6 → 7 | 5/5 → 5/5 |
Rows with a value per column of a table | 0.053 (0.035–0.185) | 0.013 (0.013–0.023) | 5 → 1 | 5/5 → 5/5 |
Type clashes between rules and columns | 0.165 (0.111–0.257) | 0.093 (0.038–0.272) | 11 → 7 | not scored |
Backend files that read or write a column | 0.088 (0.065–0.245) | 0.017 (0.013–0.017) | 6 → 1 | 4/5 → 5/5 |
Totals over the 40 runs (sums): 2.26 USD and 146 calls without webscope and 0.93 USD and 80 calls with it. The ranges do not overlap for the column sheet and the consumers question; they overlap for the module map and the type audit, where no difference can be claimed. The first run of this method (bench/results/runs.json, 2026-10-20 as well) gave the same picture: 2.02 USD against 1.00 USD in total.
Read with care:
One model, one project, and questions chosen because an adapter covers each. Questions with no adapter are not measured; the module map is the realistic one for them.
USD includes cached tokens. The first run showed cost rising with each repetition; this run does not (without webscope per repetition: 0.42, 0.38, 0.43, 0.59, 0.44 USD). The expensive repetitions are the ones with the most cache-creation tokens, which makes cache effects the probable cause of the outliers (five points per condition, not tested further); there is no trend.
«Complete answers» means every item of the hand-fixed truth was found. The one incomplete answer without webscope missed a file.
An earlier run of 24 pairs scored against adapter output is discarded: that scoring was circular. The type audit is not scored because it has no independent truth; its precision was measured separately (see Checks below).
Measured on the Go server
Measured 21 and 22 October 2026 with bench/accept.py through claude -p (Sonnet); a run that errors, times out or leaves the server unconnected never counts as a cheap success. What it shows and what it does not:
Filling a form (
bench/tasks-fill.json, a synthetic 9-field form, 5 runs each):testwithfillcost 0.091 USD (median, range 0.080 to 0.098) and 14 calls; the generic Playwright MCP cost 0.128 USD (0.107 to 0.165) and 22 calls; both answered 5 of 5 correctly. That is 29 % cheaper, with ranges that do not overlap. The agent used 11 webscope calls where onefillwould do. One task, one form, not the application above. The N and G rows come from two batches, not interleaved (bench/results/fill-wide-2026-10-22.json).A question about where a screen is used on the application above (
t1, 5 runs each): with the Go server 0.110 USD and 12 calls, with no server 0.218 USD and 11 calls, with the Python server 0.047 USD and 2 calls. The Go server is cheaper than no server and not cheaper than the Python one; the Python server's instructions send the agent to its scripts first, and whether that explains the gap was not tested.t2ran twice per condition andt3not at all: no verdict.One-shot questions (find a file, count matches, a symbol;
bench/tasks-synthetic.json): the agent never called a webscope tool in 12 of 12 runs, so there is nothing to compare. The server's own instructions say that a one-off search or read costs the same with grep and Read.Not measured: other models, other agents than Claude Code, Windows, and any claim of savings on a task that is not one of the above.
Checks
Measured on the same application, 2026-10-20, labels set by hand from the code (not by an adapter). Counts are small: read them as orders of magnitude.
audit_types: before the change, 28 of 36 labelled findings were real (78 %). The false ones came from joining a column by name across tables, from services that transform the value before the column (trueto'on'), from filter-only requests and from rules that already bound the value. It now narrows to the table of the model the request names and marks asheuristicany finding it cannot tie to one table (nested keys, no model match,boolean,in/exists/closure rules); only the others set exit code 1. On the sample used to tune it: 14 of 15 real; on 12 fresh findings: 10 of 12; together 24 of 27 (89 %). The last rule change (in/exists) was not measured on a new sample. Target was 90 %: not reached.unique_indexes: 9 of 22 findings labelled before the labeller stopped: 8 real, 1 false (CATCHES?guessed from the folder).tenant_queries: 23 of 92 lines labelled: all 23 are real default-connection queries on tenant tables; none is reachable from a job, command or export, so the risk it warns about did not show up in this application.Engines:
fixtures/laravel(Docker; Laravel 12 with MySQL 8, PostgreSQL 16 and SQLite) checks thattinker --read-onlyis refused by the database, that the default run rolls back and that only--commitpersists, by row counts. Run it withsh fixtures/laravel/run-engine-checks.sh.CI runs on GitHub (
.github/workflows/go.ymlfor the server on Ubuntu and macOS with-race, and the Docker image check;pack.ymlfor the adapters and the engine job). Windows is not covered and not validated. One test of thematrixdelete fails now and then on macOS (seedocs/known-failures.md).
Related MCP server: MCPort
Quick Start
1. Get the binary
Download a binary or .mcpb bundle from the v1.0.0 GitHub release, or build it from source (Go 1.27 or newer):
go build -o bin/webscope ./cmd/webscoperelease/build.sh builds the five release targets with checksums, release/mcpb.sh packs each one as an .mcpb bundle, and the Dockerfile builds an image. To rebuild them locally:
VERSION=1.0.0 release/build.sh dist # darwin/arm64, darwin/amd64, linux/amd64, linux/arm64, windows/amd64 + SHA256SUMS
VERSION=1.0.0 release/mcpb.sh dist dist-mcpb # one .mcpb bundle per binary (needs npx)
release/docker_check.sh # builds the image and checks it answers over stdioThe binary reports its version in serverInfo (webscope -version); a plain go build reports 1.0.0-dev, a release build sets it from VERSION.
Command line
Flag | What it does |
| Path of |
| Detects the stack and writes |
| Pack folder used by |
| Exports the |
| Checks the manifest, its scripts and the environment (Docker, PHP, Chrome). |
| Lets |
| Prints the version. |
Environment variables
Variable | Effect |
| Manifest path when |
| Offers the |
| Base URL of the app under test (overrides |
| Chrome debugging address (default |
| Profile folder of a Chrome with remote debugging switched on in |
|
|
| Allows scripts the manifest marks as sensitive. |
Scripts receive WEBSCOPE_ROOT and WEBSCOPE_MANIFEST.
Repository layout
Path | Contents |
| The binary: flags, |
| MCP stdio server and tool registry. |
| The four tools. |
| Chrome connection, manifest, root and secret sandbox, init and doctor, helpers, version. |
| JavaScript page helpers the |
| Stack pack: adapters and |
| Exported subagents and skills. |
| Test apps (ARIA, form, Vuetify) and the Laravel engine fixture. |
| Benchmark method and results. |
| Build, |
| Guides, |
2. Initialize your project
Run in your project root. It detects the stack (Laravel, Vue, Astro or Python get stack-specific scripts from the pack; anything else gets the generic set) and writes webscope.json:
bin/webscope --init --adapters /path/to/webscope/packs/laravelWithout --adapters it looks for packs/laravel beside the binary or in the current folder; with no pack it writes a manifest with no scripts. The manifest gets only the core scripts of the pack; add --all for the extra ones (the list is in docs/PURPOSE.md; they stay in the pack either way). --doctor checks the manifest, that every script file exists, and whether Chrome answers.
3. Register with your MCP client
claude mcp add --scope project webscope -- /absolute/path/to/bin/webscope -manifest "$PWD/webscope.json"Start claude and verify with /mcp: webscope is connected with search, read and run (and test when a base URL is set). Without a manifest the server refuses to start from the filesystem root.
Tools
Tool | Description |
| Case-insensitive search grouped by file with relevance ranking. |
| Surgical read: extracts a single function, method, class (PHP, TS, JS, Vue, Python), Markdown section, line range, or log tail ( |
| Runs a declared read-only script from the manifest. Output trimmed to head and tail limits. |
| Runs scripts declared in |
| Drives a tab of the app under test in a running Chrome (see below). Offered only when |
Every response starts with a one-line actionable guide and is capped at token_limit (~4 characters per token).
Browser tests: the test tool
test reaches the screen in one of two ways. Webscope never starts a browser and never touches your everyday Chrome on its own:
Claude in Chrome (first choice).
test(action="emit", args=["state"])returns a short snippet that the agent runs in your open tab with the extension's JavaScript tool. It uses your own Chrome and your login, opens no port and shows no permission prompt (see «Running in your own Chrome through another browser tool» below).A Chrome you start yourself with a debugging port and its own profile. Start it as shown under «Native Chrome Connector» (
--remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-webscope"); webscope uses the one that answers onhttp://127.0.0.1:9222(or the address inWEBSCOPE_CDP). If none answers,tabsfails with that instruction. If the app needs a login, sign in once in that window; the profile keeps it.
test attaches to one tab, runs one action and lets go; it keeps no session, so every call names its tab.
From Chrome 136 the debugging port is ignored on the default profile, so give that Chrome its own --user-data-dir. Your normal Chrome also works after you switch on chrome://inspect/#remote-debugging and set WEBSCOPE_CHROME_PROFILE to its profile folder (webscope does not look for it); it asks for permission once per server session.
Running in your own Chrome through another browser tool
test(action="emit", args=["state"]) returns a JavaScript snippet (under 500 bytes) instead of running the action. Run it in an open tab with any tool that evaluates JavaScript in the page (for example the Claude in Chrome extension's javascript_tool): it loads the page helpers from a small server webscope starts on 127.0.0.1 and calls the action, so no debugging port is needed. emit covers page actions only; tabs, newtab, newwindow, closetab and upload run inside the tool. The page's content security policy must allow fetch and eval. The result comes back as text with URL query strings cut out (some browser tools refuse results that carry them), simulate and observe run in the page this way too, and the snippet reinstalls a page helper that comes from an older script.
Which browser mode
Mode | Permission prompts | Sessions | How |
extension (default of the exported | none: the tool never opens a debugging connection | the user's own Chrome, with their logins |
|
cdp, own profile | none | a separate Chrome; sign in once, the profile keeps it | start Chrome with |
cdp, normal Chrome | one per MCP session (again after reconnecting the server or restarting Chrome) | the user's Chrome | switch on |
Use extension unless the user asks otherwise; for an isolated run with no sessions use the own-profile Chrome. upload, newtab and closetab work only in the two cdp modes.
Cost is not assumed: measure a pass in each mode with session_cost.py before quoting numbers.
Passing a module with a subagent
webscope --agent exports screenpass. It passes the list, the forms and every sub-route as bounded tasks, reports what was tested and what was not, and cleans up only
through the screen. Two actions keep a pass short: tabpass runs a whole table of rows (add, fill, edit, remove, save, read back) in one call, and purge deletes every
listed row that starts with a test prefix, whatever number of confirmations the screen asks. The agent never touches the database: leftovers (logical deletes, child rows) are listed
in its report for the main session.
Simulating server errors
test(action="simulate", tab, args=["POST /api/items", 403, "press", "Guardar"]) runs a page action while the requests that match the route (the method is optional) are answered by webscope with that HTTP status, or as a network failure when the status is 0. The server never receives them; interception ends with the call. It shows how the screen reacts to an error; it does not prove that the backend enforces a permission.
Observing what the page does
test(action="observe", tab, args=["press", "Guardar"]) runs a page action and returns its result plus everything the page did meanwhile: each fetch or XHR with its status, the body sent and the reply (cut at 2000 bytes), network failures, console errors and warnings, browser log errors and uncaught exceptions. It needs nothing installed in the page, so it also sees calls made before the helpers load. It watches one call; to cover a whole module in one go, put the steps in a single steps call and observe that.
Icon-only row buttons
inventory lists a row button with no name (no label, title, text or mdi- class, such as an SVG icon) as #N, its position in the row, and row accepts "#N" to press it.
Ask for the tool with WEBSCOPE_TEST=1 and set the app's base URL (project.base_url in the manifest or WEBSCOPE_BASE_URL); the tool drives only tabs whose URL is under it and refuses any other. WEBSCOPE_CDP changes Chrome's address (default http://127.0.0.1:9222). Then test(action="tabs") lists the tabs and the other actions take tab and args (a JSON array). The actions (state, fill, press, crud, matrix, inventory, emptySubmit, testRequired, menu, row, steps, upload, payloads, network and more) are listed in the resource webscope://test/actions. They install the same page helpers as the pack's connector (window.__f and the window.__e2e capture), so a form is filled by its visible label and a whole create-edit-delete cycle is one crud call. Actions marked in the catalog change data: use test data with a ZZ_ prefix.
newtab, newwindow and closetab open and close tabs in that same Chrome, so a pass does not borrow your working tab. A tab opened this way shares your cookies and login (the session you already have in the debugging profile); newtab with "isolated" opens it in a new browser context with no session, to check a logged-out page or to sign in as a test user without touching yours. The URL must be under the base URL, at most 5 such tabs stay open, closetab refuses any tab the tool did not open, and a shared tab on a base URL that is not this machine needs project.allow_remote: true.
Acceptance (21 Oct 2026): a haiku session with only mcp__webscope__test filled and saved the form fixture in 9 turns for 0.011 USD (bench/acceptance_test_tool.sh).
Chrome connector of the Laravel pack (e2e_chrome.py)
The pack also ships the earlier Python connector, which runs the same helpers from a script and adds route walks, replay and Playwright export:
Native Chrome Connector (--evaluate)
Runs JavaScript in a tab of a Chrome that you control, through the Chrome DevTools Protocol (CDP), so logins and 2FA survive between runs. It needs a Chrome started with remote debugging and its own profile: from Chrome 136 the debugging port is ignored on the default profile, so you sign in once in a dedicated profile that then keeps its cookies. It does not attach to your everyday Chrome window.
# macOS
open -na "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-webscope"
# Linux
google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-webscope"
# Windows
chrome.exe --remote-debugging-port=9222 --user-data-dir=%USERPROFILE%\.chrome-webscopeA new profile opens Chrome's welcome page, not your app: open the app in a tab yourself and sign in. Set the app's base URL once (project.base_url in the manifest or WEBSCOPE_BASE_URL=http://localhost:5173); without it nothing runs. Another debugging port: WEBSCOPE_CDP_PORT. A raw script takes the tab as its last argument (--evaluate "<js>" localhost:5173), the sub-commands take --url (--evaluate --map --url localhost:5173); neither ever runs in «the active tab». If the port does not answer, the command prints the launch lines above and stops: it never falls back to your normal Chrome (the AppleScript fallback was removed on 22 Oct 2026).
Checked on 2026-10-20 on macOS with Chrome 155 and a new profile (before the AppleScript fallback was removed): the port answered, a raw --evaluate and --evaluate --map --url ran over CDP against a local app (the signed-out login page), and after --evaluate --forms <route> the helper window.__f.state() returned the buttons of the login page. __f is not there until a sub-command installs it. Not checked: Linux, Windows, a signed-in session through the new profile.
--tabs lists the open tabs (title and URL, read-only) and --open <url> focuses the tab already at that URL or opens one; the URL must be under project.base_url. Without --url, the tab is found from project.base_url: exactly one tab must match, otherwise the command lists them and stops. sh docs/chrome-cold-start.sh repeats the whole path on a throwaway profile (5 steps, checked on 2026-10-20 with Chrome and CDP; not re-run after the AppleScript fallback was removed).
What sets it apart from other browser tools is not the session but what runs in the page:
# Install the helpers (`__e2e` capture and `__f`) in the tab. Give it one route: without routes it walks every route
# of the app, which can take longer than the 120 s default
python3 packs/laravel/e2e_chrome.py --evaluate --forms /demo/auth/login --url localhost:5173
# Then inspect the current screen; a raw script takes the tab as its last argument
python3 packs/laravel/e2e_chrome.py --evaluate "window.__f.state()" "localhost:5173"
# Run a complete JS test flow from a file
python3 packs/laravel/e2e_chrome.py --evaluate test_flow.js "localhost:5173"Key Capabilities
Screen & Form Helpers (
__f): Finds inputs by visible label, fills values, coordinates clicks with active settle wait (calma()), and verifies persistence.Network Capture (
__e2e): Intercepts/api/calls, latency (ms), and console errors without screenshots.Self-Healing Across Reloads: Injected helpers tolerate full page reloads (
window.location.reload()) with automatic re-initialization.Zero-Token Replay (
--replay <name>): Replays recorded user flows in one call with 0 AI tokens.Playwright Export (
--export <capture> <url>): Converts recorded steps into clean.spec.jsPlaywright tests.
The Essential Testing Contract: Full Lifecycle (CRUD + Sub-modules)
A module is never considered tested by merely reading a view or clicking a button. Webscope enforces a strict full-lifecycle standard:
Create (C): Fill mandatory and conditional fields, submit, verify HTTP 200/201, and capture the generated record ID.
Edit (U): Reopen the record, mutate fields, and verify data persistence in both UI and database.
Exercise Sub-modules & Lifecycle: Navigate sub-routes (
:id), exercise child panels/tabs (e.g. characteristics, tariffs, pre-settlements, notes, print), and trigger state transitions (e.g. approve, reopen, duplicate).Delete & Clean (D): Delete the test record, confirm UI removal, and verify database clean state (
ZZ_E2E).
Configuration (webscope.json)
All behavior is project-contained. See webscope.example.json.
{
"root": ".",
"trim": { "head": 10, "tail": 70 },
"token_limit": 5000,
"storage": ".agents/webscope",
"test_prefix": "ZZ_E2E",
"search": { "dirs": ["src"], "extensions": ["ts", "vue", "php", "py"] },
"allowed_env": ["DATABASE_URL"],
"scripts": {
"report": {
"cmd": ["python3", "-I", "scripts/report.py"],
"description": "Summarises a database table",
"usage": "<table>",
"rejects": ["--out"],
"writes": ["--fix"],
"timeout": 120
}
}
}Key | Description |
| Project root relative to manifest directory. |
| Folders outside |
| Hard limit on response size in approximate tokens. |
| Default head ( |
| Internal storage for |
| Prefix for test entities (default |
| Folders and extensions searched by default. |
| Environment variables passed to child scripts. |
| Optional list of regular expressions; every match in a script's output is replaced by |
| Fixed command list ( |
The project section
Unknown keys in project (a typo such as base_urll) are reported on stderr when the server starts and by webscope --doctor, which also checks that the manifest parses, that each script file exists, that docker or php are installed when the scripts need them, and whether a Chrome answers on the debugging port.
The included adapters read the project section (container, boot code, module folders, frontends, audit settings…). Every key is optional and has a default. Each one is documented, with its default, who uses it and a minimal example, in docs/project.md.
Database adapters run inside a transaction that is rolled back. sql also puts the database session in read-only mode, so the database itself refuses writes. No adapter commits: tinker always rolls back and cleanup_test_rows only lists. Test data is deleted through the app's own screen; a screen that cannot delete it is a defect to report.
Included Adapters ({adapters})
Ready-made scripts of the Laravel pack (packs/laravel/). Use {adapters} in cmd; it resolves to the manifest's adapters_dir, by default packs/laravel beside the manifest:
Grouped by the stack they need. The core tools (search, read, run) need none.
Any stack
Script | Purpose |
| Browser walk, form automation ( |
| Classifies failing pieces of a run summary as known, CHANGED or NEW ( |
| Token and USD cost auditor per model for Claude Code sessions. |
| Subagent usage, webscope usage and session bootstrap for Claude Code. |
Laravel (a database adapter needs project.container or project.workdir)
Script | Purpose |
| Resolves endpoint → controller → service → model → database table. |
| For each select of a module's forms: store list → endpoint → controller → service → table and owning module; a list it cannot follow is reported as unresolved. |
| Ordered test plan for a Vue module: its kind (crud, actions, report, wizard) deduced from the code, the test types that apply with the webscope call for each, and which ones write. A heuristic; |
| Runs PHP in |
| Read-only SQL (one SELECT/SHOW/DESCRIBE/EXPLAIN/WITH) through |
| Column sheet of a table: type, nullability, rows with a value, backend readers and writers. |
| Who reads and who writes a column: |
| Unique indexes on tables with a status column and whether the backend covers each ( |
|
|
| Snapshot and diff of a query: what an on-screen action really wrote. |
| SELECT-only database integrity checks by prefix marker from |
| Compares captures against FormRequest rules, Resources, and columns. |
| Lists (read-only) the test rows matching the test prefix. |
Laravel + Vue
Script | Purpose |
| Screen → frontend module → endpoints → backend module → tables ( |
| Crosses columns with FormRequest rules, Resource casts and frontend numeric conversions, with real data. |
| Lists what could break in a module from request, form and schema; |
| Specs affected by uncommitted changes, resolved to screens ( |
Vue 3 + TS: vue_ts.py (map) maps routes, subroutes, clickable actions, API calls and E2E coverage.
Astro: astro.py (map_web) maps file-based routes, components, form fields and client scripts.
Subagents Integration
Webscope keeps subagents light. Two separate numbers:
The server itself costs about 1.6k tokens per session:
tools/listplusinstructions, measured 2026-10-20 with a 23-script manifest (the adapters it declares plus the project's own scripts) and with the 25-scriptwebscope.example.json. It does not grow with the number of included adapters: they are not intools/list; only the scripts your manifest declares add a short line toinstructions.A subagent that declares
tools:in its frontmatter loads about 9.3k tokens at start, against 28.8k when it loads every tool and skill name. That number depends on how many tools the agent declares, not on webscope's adapters.
Exporting the walkthrough and screenpass Agents
Export the ready-made agents without overwriting existing ones. walkthrough walks a module with the pack's scripts; screenpass passes one screen with the test tool only (inventory, validation, create and edit, the API calls of each save, simulated server errors) and keeps the agent inside the project instead of your global agents:
# Autodetects .agents/ or .claude/agents/
bin/webscope --agent
# Or targeting a specific folder
bin/webscope --agent [directory]Non-destructive: Never overwrites existing agent files unless
--forceis passed.Isolation: Leaves all other user-defined agents untouched.
Declaring Tools in Agents
In your .agents/<name>.md or .claude/agents/<name>.md:
---
name: browser
tools: Read, Grep, Glob, mcp__webscope__search, mcp__webscope__read, mcp__webscope__run
model: sonnet
effort: high
---Configuration in Other Clients
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"webscope": {
"command": "/absolute/path/to/bin/webscope",
"args": ["-manifest", "/absolute/path/to/webscope.json"]
}
}
}Cursor / VS Code / Cline (.cursor/mcp.json or .vscode/mcp.json)
{
"mcpServers": {
"webscope": {
"command": "/absolute/path/to/bin/webscope",
"args": ["-manifest", "${workspaceFolder}/webscope.json"]
}
}
}Zed (settings.json)
{
"context_servers": {
"webscope": {
"command": {
"path": "/absolute/path/to/bin/webscope",
"args": ["-manifest", "/absolute/path/to/webscope.json"]
}
}
}
}Updating
Rebuild (go build -o bin/webscope ./cmd/webscope) or replace the binary, then restart the client.
Security Model
Root Sandbox:
searchandreadresolve strictly withinrootand reject outside paths.Invisible Secrets: Automatically hides
.env*,*.pem,*.key,*.p12,id_rsa*,id_ed25519*,auth.json, and credentials.No Shell Injection: Scripts execute as fixed argument lists without intermediate shell.
Read-Only Default: Destructive arguments (
writes) requirerun_write,WEBSCOPE_WRITE=1, and explicit approval.SQL guard (
sql.py): a regex accepts one statement starting with SELECT, SHOW, DESCRIBE, DESC, EXPLAIN or WITH and rejects;. It is a convenience filter, not a parser:WITH ... DELETEpasses it. The real guard is that the database session is read-only (SET SESSION TRANSACTION READ ONLYon MySQL/MariaDB,default_transaction_read_onlyon PostgreSQL,query_onlyon SQLite) inside a rolled-back transaction. Verified on MySQL: aWITH ... DELETEfails with «Cannot execute statement in a READ ONLY transaction». Not covered by rollback: file output such asSELECT ... INTO OUTFILE, locks and sleeps, PostgreSQL sequences and non-transactional engines. For a hard guarantee, connect with a database user that only has SELECT.allowed_envis a deliberate escape. Scripts get onlyPATH,HOME,LANG,LC_ALL,TMPDIRand the variables you list inallowed_env(for exampleDATABASE_URL). Those values are real, and the output of a script goes back to the agent. Webscope does not redact script output: «Invisible Secrets» applies tosearchandreadonly. A script that dumps its environment, or a stack trace that prints a DSN, reaches the agent. Declare only what a script needs. Onlycleanup_test_rows.pymasks, on its own, the values of columns that look like secrets (pass,token,secret,clave).tinker.pyonly runs PHP files from the system temp folder or the project's storage folder, never arbitrary project code.
Recommended .gitignore
# Temporary Webscope artifacts
captures/
.webscope/captures/Commit webscope.json, .claude/webscope/walkthroughs/ (or .agents/webscope/), and module-map.json.
Tests
go vet ./... && go test -race ./... # server, browser tests need Chrome (WEBSCOPE_REQUIRE_CHROME=1 fails instead of skipping)
python3 -I packs/laravel/test_pack.py # the pack's adapters
sh fixtures/laravel/run-engine-checks.sh # Docker: MySQL, PostgreSQL and SQLite
release/docker_check.sh # Docker: the image answers over stdioLicense
Available Tools
3 toolsreadRead a chunk of a fileARead-only
Only a chunk of a file: the function or class symbol (PHP, TS, JS, Vue, Python; list = several in one call), the section of a .md, a range lines, or from a log the last tail or those containing contains; without anything, the file index. Use it instead of Read, cat or sed. Read-only, inside the root; does not read secrets.
| Name | Required | Description | Default |
|---|---|---|---|
| file | Yes | path relative to the root | |
| tail | No | logs: last N lines | |
| lines | No | range «120-180» | |
| format | No | concise = essentials only | detailed |
| symbol | No | function, class or title; list = several | |
| contains | No | logs: lines containing this text | |
| max_lines | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description reinforces read-only while adding genuinely new behavioral constraints: it stays "inside the root" and "does not read secrets." That sandboxing/security context is not derivable from the annotations, though return format and size limits (max_lines default 120) are not elaborated.
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?
It is a single dense sentence with no filler, front-loading the scoping constraint ("Only a chunk of a file") and then the modes. The semicolon-chained clause style is compact but somewhat cryptic, which slightly hurts parseability.
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?
For a 7-parameter read tool with no output schema, the description covers the modes, the safety profile, and the root/secret boundaries. It is nearly complete, with only minor gaps around output shape and truncation behavior left implicit.
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 coverage is 86%, so the baseline is 3, but the description adds mode-selection semantics the schema does not spell out — which parameter activates which behavior and that the file index is returned when nothing is supplied. This helps an agent choose parameters rather than just parse them.
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 names a specific verb (read) and enumerates the concrete modes — symbol extraction, .md section, line range, log tail/contains, or default file index — so the agent knows exactly what content it returns. It is clear but does not differentiate itself from the sibling `search` tool, only from external CLI tools (Read/cat/sed).
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?
"Use it instead of Read, cat or sed" gives a when-to-use directive, but the alternatives named are external tools rather than the sibling MCP tools `search` and `run`. There is no stated condition for choosing `read` over `search`, so usage is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
runRun a read-only scriptARead-only
Runs a read-only script from the manifest: exit code and trimmed output. Without name it lists them. Use it instead of Bash for what a script covers. No shell; rejects absolute paths, .. and anything that writes (goes through run_write).
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | one argument per element; relative paths, no .. | |
| name | No | script or «list» | list |
| detail | No | schema = card without running it | name |
Output Schema
| Name | Required | Description |
|---|---|---|
| exit | No | |
| note | No | |
| output | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint/openWorldHint, so the description earns credit by disclosing enforcement details: no shell, rejection of absolute paths and `..`, write attempts redirected to run_write, and the return shape (exit code plus trimmed output). This is meaningful beyond the structured fields, though permission/auth requirements and failure semantics are not covered.
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?
Three tight sentences, each earning its place, with the primary action front-loaded and the constraints (no shell, no absolute paths, no writes) following immediately. No filler.
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 annotations and an output schema present, the description carries only the behavioral burden it needs to, and it does so well for a read-only execution tool. The one gap is the undefined 'manifest' — an agent is told scripts exist but not where they come from, aside from the built-in list mode.
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 coverage is 100%, so the baseline is 3, but the description adds non-obvious semantics the schema does not: omitting `name` triggers listing, and `args` values must be relative without `..`. It still says nothing about `detail`, which the schema carries on its own.
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?
States a specific verb and resource ('Runs a read-only script from the manifest') plus the dual mode (run vs. list when name is omitted). It does not explicitly differentiate itself from the siblings `search` and `read`, and 'manifest' is left undefined, but the core action is unambiguous.
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?
Gives a clear routing rule against a comparable tool: 'Use it instead of Bash for what a script covers,' and explains the no-argument listing behavior. It stops short of naming the actual siblings (`search`, `read`) or stating when those are preferred over running a script.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchSearch the projectARead-only
Where a text is defined or used: lines grouped by file, most cited first (concise: 5 files). Case-insensitive. Use it instead of grep. To see the code use read. Read-only, inside the root; skips secrets (.env, *.key...).
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | literal unless regex=true | |
| limit | No | maximum number of lines | |
| regex | No | text is an extended regex | |
| where | No | folder or file relative to the root | |
| format | No | concise = essentials only | detailed |
| context | No | lines around |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, but the description adds real behavior beyond them: case-insensitive matching, results grouped by file and ordered by citation count, secret-file skipping (.env, *.key), and root confinement. The only weak spot is '(concise: 5 files)', which is ambiguous against the schema's limit-as-lines semantics.
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?
Compact and front-loaded: purpose first, then matching/ordering behavior, then routing to read. Every sentence carries information, though the telegraphic '(concise: 5 files)' fragment is cryptic rather than concise.
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 usefully sketches return shape (grouped lines, ranked by citations) and scope limits, and annotations cover the safety profile. Minor gaps remain around how limit/context interact with the concise format, but nothing critical for invocation is missing.
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 100%, so all six parameters are documented structurally, making 3 the baseline. The description adds only marginal meaning (case-insensitivity for text, concise-mode behavior for format) and never mentions regex or where, so it does not exceed the schema.
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?
States a specific verb and resource ('where a text is defined or used') plus the result shape ('lines grouped by file, most cited first'), which an agent can distinguish from read and run without opening a schema. The scope ('inside the root') and matching mode ('case-insensitive') sharpen it further.
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?
Explicitly routes the agent: 'Use it instead of grep' and 'To see the code use read.' It names the alternative sibling and the condition that selects it, leaving little to inference.
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.
6 tool updates
v0.5.1- Removed
buscar - Removed
correr - Removed
leer - Added
read - Added
run - Added
search
3 tool updates
v0.2.0- First observed
buscar - First observed
correr - First observed
leer
TDQS
Scored across 3 tools
search, read, and run have clearly distinct roles—locating text, reading file content, and executing read-only scripts—and the descriptions explicitly steer agents away from overlap (e.g., 'To see the code use read', 'use it instead of grep'). No tool pair does the same job.
All tools use a single lowercase verb (search, read, run), a consistent and predictable convention. There is no mixed casing or noun/verb style inconsistency.
Three tools is well-scoped for a focused read-only code exploration server; each tool is distinct and earns its place. The count is not thin because each tool packs several related capabilities (read covers symbols, ranges, sections, logs, and index).
The surface covers text search, file/symbol/range reading, and read-only script execution for codebase exploration. A minor gap is the lack of a dedicated filename/glob search, though read's file index and search partially cover that need.
Maintenance
Related MCP Connectors
Project memory, documentation and controlled secret access for AI coding agents.
Codebase intelligence for agents: 152 structured artifacts across 21 programs, one call.
Securely search and manage workspace context files for AI agents and teams.
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
Related MCP Servers
- FlicenseAqualityBmaintenanceEnables coding agents to perform workspace-confined file operations, read-only Git inspection, and structured shell commands, while requiring out-of-band human approval for mutations and external executions and maintaining an audit trail.143-
- AlicenseNot gradedqualityBmaintenanceEnables AI clients to safely read, search, understand, and edit local project code and files, with Git inspection, code indexing, and controlled command execution within permissioned workspaces.4 npm6MIT
- AlicenseAqualityBmaintenanceEnables coding agents to safely inspect and modify project files within explicitly allowed workspace roots, with controls for file operations, git status/diff, and optionally allowlisted command execution.11Apache 2.0
- FlicenseNot gradedqualityBmaintenanceEnables AI coding agents to retrieve focused, bounded repository context locally and read-only, reducing token usage while never modifying the repository or executing its code.-