periscope-mcp
Pruebas de sitios web diseñadas para agentes de IA: 63 herramientas de Playwright con aserciones estrictas, autocompletado de formularios, sesiones de autenticación, simulación de red y auditorías de accesibilidad/SEO/GEO + Lighthouse.
Documentación
periscope-mcp
Un servidor MCP que brinda a los agentes de IA 74 herramientas de Playwright para control de calidad, pruebas y análisis de aplicaciones web — sitios estáticos, SPAs y aplicaciones detrás de un inicio de sesión — devolviendo veredictos contundentes, no capturas de pantalla que hay que entrecerrar los ojos para ver. No es un envoltorio delgado alrededor de las APIs del navegador; las herramientas están diseñadas en torno a cómo trabajan realmente los agentes:
- Resultados contundentes, no capturas que requieren entrecerrar los ojos —
assert_conditiondevuelvepassed: true/falsecon el valor real; las comprobaciones devuelven problemas estructurados. - Una llamada en lugar de diez —
auto_fill_formdetecta, infiere y completa un formulario completo;interact_and_testagrupa 25 tipos de acciones con comprobaciones;test_projectrastrea y audita un sitio completo. - Pruebas reales de aplicaciones web — sesiones autenticadas persistentes (autenticación de formulario/básica/cookie, además de un inicio de sesión interactivo visible para 2FA/SSO/CAPTCHA que luego se ejecuta sin interfaz gráfica), flujos de varios pasos, simulación de red, instantáneas de estado e INP real medido a partir de las interacciones que impulsa.
- Respuestas honestas — los fallos indican qué sucedió y qué hacer a continuación (sesión caducada vs. fallo del navegador vs. desalojo); las operaciones silenciosas sin efecto, como arrastres ignorados, se devuelven marcadas, no como éxito falso.
- Depuración integrada — cuerpos de respuesta de API capturados, registros de consola/red, simulación de red e instantáneas/diferencias de estado, sin necesidad de llamadas de configuración.
- Auditorías que los agentes no pueden obtener de un enlace de navegador — accesibilidad, SEO y preparación para GEO/búsqueda agéntica (acceso de rastreadores de IA en robots.txt, llms.txt, WebMCP), además de Lighthouse real.
Playwright + Chrome sin interfaz gráfica debajo; rastreo de sitios, pruebas responsivas y comparación de capturas de pantalla encima. Funciona con cualquier cliente MCP — Claude Code, Codex, Cursor, Windsurf, Gemini CLI, agentes personalizados o cualquier otra cosa que hable MCP a través de stdio.
¿Por qué no simplemente playwright-mcp?
playwright-mcp es excelente en lo que es: control general del navegador a través de MCP, con herramientas que reflejan la propia API de Playwright. Si la tarea es "navega por este sitio, haz clic, extrae algo", úsalo.
Periscope existe para una tarea diferente: probar y auditar un sitio o aplicación web, y luego informar los hallazgos — y sus herramientas codifican el conocimiento de pruebas que un agente tendría que reinventar en cada sesión:
| Control bruto del navegador | Periscope | |
|---|---|---|
| Verificar un resultado | Leer una captura de pantalla o volcado de DOM y juzgar | assert_condition → passed: true/false contundente + valor real |
| Completar un formulario | Una llamada por campo, el agente inventa datos de prueba | auto_fill_form — detecta campos, infiere datos realistas, informa fallos por campo |
| Autenticación | Volver a iniciar sesión mediante clics programados en cada sesión | Los proyectos persisten autenticación de formulario/básica/cookie; las sesiones comparten el contexto de inicio de sesión |
| Auditoría de todo el sitio | Recorrer páginas manualmente | test_project — rastreo + comprobaciones de accesibilidad/SEO/GEO/visual/funcionalidad + informe guardado |
| Diagnosticar una página rota | Pedir registros, reproducir solicitudes | Los cuerpos de respuesta, consola y red se capturan automáticamente; simula APIs con intercept_network |
| Fallos silenciosos | El arrastre "tiene éxito", nada se mueve | Marcado en el resultado, con la ruta de recuperación explicada |
| Auditorías de preparación para IA | — | Acceso de rastreadores de IA en robots.txt, llms.txt, anotaciones WebMCP, JSON-LD, además de puntuaciones reales de Lighthouse |
Los dos no son rivales — un agente puede usar felizmente playwright-mcp para tareas de navegación y Periscope cuando lleva el sombrero de control de calidad. Las apuestas de diseño de Periscope se centran simplemente en ese sombrero: menos llamadas de mayor nivel; veredictos estructurados en lugar de estado de página bruto; y errores escritos para decirle al agente qué hacer a continuación.
Arquitectura
MCP client (AI agent) --> MCP Server (stdio) --> Playwright (Headless Chrome)
| |
+-- Projects (JSON) +-- Persistent Sessions
+-- Screenshots (PNG) +-- Network Interception
+-- Reports (JSON) +-- Device Emulation
+-- Videos (WebM)
Cómo funciona: tu cliente MCP se conecta a este servidor a través de stdio. El servidor expone 74 herramientas que el agente puede llamar para crear proyectos, configurar autenticación, rastrear sitios web, ejecutar comprobaciones estáticas y probar interactivamente aplicaciones web usando sesiones de navegador persistentes. Los resultados (JSON + capturas de pantalla + videos) se devuelven al agente para su análisis.
Estructura del Proyecto
periscope-mcp/
├── server.py # MCP server entry point (stdio wiring + dispatch)
├── tool_schemas.py # All 74 MCP tool definitions (schemas)
├── runtime.py # Shared singletons (project store, sessions, browser)
├── coercion.py # Argument coercion for MCP clients with stale schemas
├── handlers/ # Tool handlers, grouped by category
│ ├── registry.py # @tool(name) decorator + HANDLERS registry
│ ├── projects.py # create/list/get/delete project
│ ├── auth.py # form login, basic auth, cookies, copy_auth
│ ├── static_testing.py # test_url, crawl, test_project, reports, responsive
│ ├── session_tools.py # open/close/list sessions, viewport, history
│ ├── interactive.py # click, fill, steps, element queries, dialogs
│ ├── analysis.py # forms, links, keyboard nav, tables, toasts, contrast
│ ├── advanced.py # network mocking, storage, iframes, emulation, recording
│ ├── agent_speed.py # assertions, smart find, auto-fill, snapshots
│ ├── web.py # web_search, web_fetch
│ ├── discovery.py # describe_tools catalog
│ └── system.py # periscope_system: status, self-update, agents_md
├── tester.py # Playwright browser control + test orchestration
├── crawler.py # Page discovery (BFS crawl, same-domain only)
├── projects.py # Project CRUD + auth config storage
├── auth.py # Authentication handlers (form, basic, cookies)
├── sessions.py # SessionManager + PageSession — persistent page lifecycle
├── interactions.py # Interaction primitives (click, fill, execute_steps)
├── utils.py # Screenshot comparison (Pillow pixel diff)
├── config.py # Global settings (timeouts, paths, session limits)
├── checks/
│ ├── visual.py # Broken images, favicon, overflow, small text
│ ├── accessibility.py # Alt text, labels, headings, lang, ARIA, keyboard nav
│ ├── functionality.py # Broken links, forms, SEO, performance, link checker
│ └── geo.py # GEO/agentic search: robots.txt AI crawlers, llms.txt, WebMCP, JSON-LD
├── tests/ # Unit tests (no browser) + tests/e2e/ (real browser + fixture pages)
├── data/ # Created at runtime (gitignored — contains credentials)
├── Dockerfile
├── docker-compose.yml
└── .mcp.json.example # MCP registration template (copy to .mcp.json)
Requisitos previos
- Python 3.11+
- Playwright + navegador Chromium
Instalación (Local)
Instalación rápida (Debian/Ubuntu)
Un comando — clona e instala:
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh
Totalmente desatendido (sin indicaciones de confirmación):
git clone https://github.com/segentic-lab/periscope-mcp.git && cd periscope-mcp && ./install.sh -y
¿Ya lo clonaste? Solo ejecuta ./install.sh desde el directorio del repositorio.
El script instala los requisitos previos de apt, crea el entorno virtual, instala las dependencias de Python y el Chromium de Playwright, ejecuta una autoprueba sin interfaz gráfica y genera mcp-config.json con las rutas absolutas correctas para esta instalación (cópialo o combínalo en el .mcp.json de tu proyecto). Banderas útiles:
./install.sh --system-chromium— usa un Chromium/Chrome existente (estableceCHROMIUM_PATH) en lugar de descargar la compilación de Playwright./install.sh --skip-deps— nunca tocar apt / usar sudo./install.sh -y— no interactivo (sin indicaciones de confirmación)
En cualquier otra plataforma, el script no modifica tu sistema — imprime los comandos exactos para ejecutar en tu sistema operativo (./install.sh --manual macos|fedora|arch|suse|windows para elegir explícitamente).
Actualización
./update.sh
Obtiene la última fuente de GitHub (git pull --ff-only) y actualiza la instalación: dependencias de Python, navegador de Playwright (se mantiene en Chromium del sistema si esa es la instalación que se usa), el registro + autoprueba de lanzamiento sin interfaz gráfica, y un mcp-config.json regenerado. Funciona en cualquier plataforma con una instalación existente. Tu directorio data/ (proyectos, credenciales, capturas de pantalla, informes) nunca se toca.
./update.sh --force— guarda las modificaciones locales en archivos rastreados primero (recupera congit stash pop)./update.sh --full— también vuelve a verificar los requisitos previos de apt en Debian/Ubuntu (usa sudo)
Si tienes modificaciones locales, el script se niega y las lista en lugar de sobrescribirlas.
Instalación manual
# Clone the repo
cd periscope-mcp
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install Chromium for Playwright
playwright install chromium
Instalación (Docker)
docker compose up -d
Consulta la sección Implementación con Docker a continuación.
Conexión de un Cliente MCP
Periscope es un servidor MCP estándar de stdio: apunta cualquier cliente MCP a venv/bin/python server.py y listo. ./install.sh genera mcp-config.json con las rutas absolutas correctas para tu máquina; la mayoría de los clientes aceptan ese formato directamente:
{
"mcpServers": {
"periscope": {
"command": "/path/to/periscope-mcp/venv/bin/python",
"args": ["/path/to/periscope-mcp/server.py"]
}
}
}
Ejemplos específicos por cliente:
- Claude Code — copia la configuración en el proyecto como
.mcp.json(cp .mcp.json.example .mcp.jsony ajusta las rutas), o ejecutaclaude mcp add periscope -- /path/to/venv/bin/python /path/to/server.py - Cursor / Windsurf — agrega el bloque anterior a
~/.cursor/mcp.json/~/.codeium/windsurf/mcp_config.json - Codex CLI — agrega a
~/.codex/config.toml:[mcp_servers.periscope]concommandyargscomo arriba - Agentes personalizados — cualquier cliente del SDK de MCP puede iniciar el servidor a través de stdio con el mismo comando y argumentos
Después de configurar, reinicia tu cliente.
Enseñar a tu agente a usar las herramientas
Dos opciones, según tu agente:
-
Claude Code (recomendado: instala la habilidad).
SKILL.md(raíz del repositorio; también expuesto enskills/periscope/en el diseño de habilidades de Claude Code) es una habilidad de Claude Code — se activa automáticamente en tareas de pruebas web y carga una guía operativa destilada (tabla de decisión de flujos de trabajo + los errores comunes) solo cuando es necesario, costando ~0 contexto en otros casos:ln -s "$(pwd)/skills/periscope" ~/.claude/skills/periscopeUn enlace simbólico lo mantiene actualizado con
./update.sh(copia la carpeta en su lugar si prefieres una versión congelada). -
Cualquier otro cliente MCP: pega la guía.
AGENTS.mdcontiene un bloque de indicación de sistema listo para usar — flujos de trabajo, orientación de selección de herramientas y errores comunes conocidos. Pega su contenido en la indicación de sistema de tu agente (o instrucciones personalizadas).
De cualquier manera, el agente siempre puede obtener la guía completa actual del servidor en ejecución a través de periscope_system(action="agents_md") y el catálogo completo a través de describe_tools().
Referencia de Herramientas MCP (74 herramientas)
Gestión de Proyectos (4 herramientas)
| Herramienta | Descripción | Parámetros Requeridos |
|---|---|---|
create_project | Crear un nuevo proyecto de pruebas | name, base_url |
list_projects | Listar todos los proyectos | (ninguno) |
get_project | Obtener detalles del proyecto | name |
delete_project | Eliminar proyecto + datos | name |
Autenticación (7 herramientas)
| Herramienta | Descripción | Parámetros Requeridos |
|---|---|---|
set_form_login | Configurar inicio de sesión de formulario con nombre de usuario/contraseña | project, login_url, username, password |
set_basic_auth | Configurar autenticación básica HTTP | project, username, password |
set_cookies | Inyectar cookies de sesión | project, cookies (matriz) |
login_project | Ejecutar inicio de sesión usando la autenticación configurada | project |
interactive_login | Abrir una ventana visible para iniciar sesión manualmente (2FA/SSO/CAPTCHA), luego save_login | project |
save_login | Capturar la sesión de inicio de sesión manual; el proyecto luego se ejecuta autenticado + sin interfaz gráfica | project |
copy_auth | Copiar configuración de autenticación + estado de sesión entre proyectos | from_project, to_project |
Para inicios de sesión que no se pueden automatizar — 2FA/MFA, redirecciones SSO/OAuth, CAPTCHA, enlaces mágicos — usa interactive_login (abre una ventana de navegador real; requiere una pantalla en el servidor), completa el inicio de sesión tú mismo, luego save_login. Captura la sesión autenticada (cookies + localStorage) en el proyecto, y cada sesión futura sin interfaz gráfica la reutiliza. Vuelve a ejecutarlo cuando la sesión caduque (Periscope lo marca automáticamente — consulta la detección de caducidad de autenticación en test_project).
Pruebas Estáticas (3 herramientas)
| Herramienta | Descripción | Parámetros Requeridos |
|---|---|---|
test_url | Probar una sola URL (captura de pantalla + comprobaciones) | url |
crawl_project | Descubrir todas las páginas desde la URL base | project |
test_project | Auditoría completa: rastreo + prueba de todas las páginas | project |
Resultados (4 herramientas)
| Herramienta | Descripción | Parámetros Requeridos |
|---|---|---|
get_screenshot | Obtener ruta del archivo de captura de pantalla | project, url |
list_reports | Listar informes de prueba guardados | (opcional: project) |
get_report | Leer un archivo de informe | report_path |
session_report | Expediente HTML+PDF de cada llamada de herramienta en esta ejecución — argumentos (redactados), veredictos, tiempos, capturas de pantalla | (ninguno) |
Gestión de Sesiones (5 herramientas)
Las sesiones mantienen las páginas del navegador vivas entre llamadas de herramientas, lo que permite flujos de trabajo interactivos de varios pasos.
| Herramienta | Descripción | Parámetros Requeridos |
|---|---|---|
open_session | Abrir sesión de navegador persistente (headed=true para una ventana visible) | url |
close_session | Cerrar sesión y liberar recursos | session_id |
list_sessions | Listar todas las sesiones activas | (ninguno) |
set_viewport | Cambiar tamaño de viewport (8 ajustes preestablecidos de dispositivo o ancho/alto personalizado) | session_id |
select_page | Adoptar una ventana emergente/nueva pestaña (OAuth, target=_blank) como una nueva sesión manejable | session_id |
Ajustes preestablecidos de set_viewport: mobile_sm (320x568), mobile (375x812), mobile_lg (428x926), tablet (768x1024), tablet_lg (1024x1366), laptop (1366x768), desktop (1920x1080), desktop_lg (2560x1440)
Acciones Interactivas (7 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
click_element | Hacer clic en un elemento (force=true omite superposiciones) | session_id, selector |
fill_form | Rellenar campos de formulario, opcionalmente enviar | session_id, fields |
select_option | <select> nativo o menú desplegable personalizado (Radix/shadcn) — detección automática | session_id, selector |
interact_and_test | Flujo de trabajo de múltiples pasos con 25 acciones (ver más abajo) | steps |
get_page_elements | Listar elementos coincidentes con atributos | selector |
flow | Guardar / ejecutar / listar / eliminar secuencias de pasos con nombre (flujos de trabajo reutilizables) | (varía según la acción) |
scroll_into_view | Desplazar elemento al viewport sin hacer clic | session_id, selector |
interact_and_test admite 25 acciones de paso:
click, force_click, fill, force_fill, type, select, select_option, wait, wait_for, wait_for_text, screenshot, navigate, hover, press_key, check, uncheck, scroll_to, scroll_within, evaluate_js, drag, right_click, go_back, go_forward, upload_file, wait_for_network
Análisis (10 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
test_form_validation | Analizar mensajes de validación de formularios | (url o session_id) |
compare_screenshots | Diferencia de píxeles entre dos capturas de pantalla | screenshot1, screenshot2 |
visual_check | Líneas base de regresión visual con nombre: configurar una vez, verificar aprobado/reprobado | session_id, name |
test_responsive | Probar en viewports móvil/tableta/escritorio | url |
check_links | Verificador integral de enlaces (internos + externos) | (url o session_id) |
measure_interaction | Medir tiempo de clic a resultado | session_id, selector |
get_table_data | Analizar tabla HTML a JSON estructurado (encabezados → valores de celda) | session_id |
get_toast_messages | Capturar mensajes visibles de toast/notificación | session_id |
run_lighthouse | Auditoría real de Google Lighthouse: puntuaciones 0-100, Core Web Vitals, auditorías fallidas (requiere Node.js) | url |
get_interaction_log | Exportar serie temporal real de INP (por interacción) como JSON/CSV + estadísticas de percentiles | session_id |
Velocidad de flujo de trabajo (8 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
screenshot_session | Captura rápida del estado actual de la página | session_id |
run_checks_on_session | Ejecutar verificaciones en sesión activa (sin página nueva) | session_id |
navigate_session | Historial del navegador: atrás, adelante o recargar | session_id, action |
handle_dialog | Aceptar/descartar alerta/confirmación/prompt de JS (llamar ANTES del disparador) | session_id, action |
upload_file | Establecer archivo(s) en <input type="file"> | session_id, selector, files |
wait_for_network | Esperar a que un patrón de URL de API específico se complete | session_id, url_pattern |
wait_for_gone | Esperar a que un elemento desaparezca (cierre de modal, spinner desaparecido) | session_id, selector |
get_page_html | outerHTML crudo de elementos, o HTML completo de la página | session_id |
Pruebas avanzadas (9 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
intercept_network | Simular respuestas de API (probar estados de error/vacío/carga) | session_id, url_pattern |
clear_intercepts | Eliminar simulaciones de red (todas, o por patrón) | session_id |
get_local_storage | Leer localStorage o sessionStorage | session_id |
set_local_storage | Escribir en localStorage o sessionStorage | session_id, entries |
select_iframe | Cambiar al contenido de iframe (devuelve nueva sesión) | session_id, selector |
get_computed_style | Obtener valores CSS renderizados reales | session_id, selector, properties |
emulate_network | Limitar red: slow_3g, fast_3g, offline, reset | session_id, preset |
test_dark_mode | Alternar prefers-color-scheme oscuro/claro | session_id, mode |
download_file | Hacer clic en un disparador y capturar el archivo descargado (ruta, sha256, vista previa de texto) | session_id, selector |
Grabación y consola (3 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
record_session | Grabar flujo de trabajo como video | url, steps |
test_keyboard_navigation | Auditoría de orden de tabulación e indicador de enfoque | (url o session_id) |
get_console_errors | Obtener todos los errores/registros de consola (monitoreo pasivo) | session_id |
Herramientas de velocidad para agentes de IA (10 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
assert_condition | Aprobado/reprobado programático: text_contains, element_exists, url_contains, etc. | session_id, assertion |
assert_all | Aserciones por lotes — todos los veredictos en una llamada, sin aborto temprano | session_id, assertions |
get_page_map | Mapa semántico de página: roles, nombres, estados + selectores listos en una llamada | session_id |
find_element | Buscador inteligente por texto, etiqueta, rol o proximidad a otro elemento | session_id |
auto_fill_form | Auto-detección de campos, inferencia de tipos, relleno con datos de prueba. Una llamada = muchos rellenos. | session_id |
get_network_log | Todas las solicitudes de red capturadas (URL, estado, método, tipo) | session_id |
get_response_body | Cuerpo de texto real de respuesta de API (diagnosticar errores 400/500) | session_id, url_pattern |
page_state | Puntos de control con nombre: instantánea / restaurar / comparar estado de página | session_id, action, name |
get_cookies | Leer todas las cookies de la sesión | session_id |
check_color_contrast | Verificaciones de relación de contraste WCAG AA/AAA en elementos de texto | session_id |
Web, descubrimiento y sistema (4 herramientas)
| Herramienta | Descripción | Parámetros requeridos |
|---|---|---|
web_search | Buscar en DuckDuckGo: títulos + URLs + fragmentos | query |
web_fetch | Obtener URL → Markdown legible (o texto/html); render=true ejecuta JS en Chromium sin interfaz (+ project para detrás de inicio de sesión), contains controla la obtención, save escribe un artefacto .md limpio | url |
describe_tools | Catálogo estructurado de todas las herramientas con flujos de trabajo y consejos | (ninguno) |
periscope_system | Estado de instalación + verificación/aplicación de actualizaciones + obtener AGENTS.md actual | (ninguno) |
Verificaciones de prueba
Visual (checks/visual.py)
- Imágenes rotas (carga incompleta o ancho natural 0)
- Favicon faltante
- Desbordamiento horizontal / problemas de diseño
- Texto muy pequeño (< 12px)
- Color de fondo del cuerpo faltante
- Imágenes sin dimensiones explícitas de ancho/alto
Accesibilidad (checks/accessibility.py)
- Imágenes sin texto
alt(imágenes decorativas exentas:alt="",role="presentation"/"none",aria-hidden) - Enlaces y botones sin nombres accesibles (verifica texto,
aria-label,aria-labelledbyresoluble,title,img[alt], svg<title>; elementosaria-hiddenexentos) - Entradas de formulario sin etiquetas asociadas (
label[for], etiqueta envolvente,aria-label/aria-labelledby,title) - Jerarquía de encabezados (H1 faltante, múltiples H1, niveles omitidos)
- Atributo
langfaltante en<html> - Valores
idduplicados (rompenlabel[for]y referencias aria) - Validez de ARIA: valores
roledesconocidos, referenciasaria-labelledby/describedby/controls/owns/activedescendanta ids inexistentes - Enlace de navegación de salto faltante (escanea los primeros 5 enlaces)
- Elementos con
tabindex > 0 - Auditoría de navegación por teclado (orden de tabulación, indicadores de enfoque visibles, detección de ciclo de identidad de elementos) — mediante herramienta
test_keyboard_navigation
Funcionalidad (checks/functionality.py)
- Enlaces internos rotos (verificación HTTP HEAD, hasta 20 enlaces en
check_functionality) - Verificador integral de enlaces con soporte de enlaces externos (hasta 100 enlaces) — mediante herramienta
check_links - Formularios sin acción o botón de envío
- Botones huérfanos fuera de formularios
- Enlaces externos sin
target="_blank" - Conteo de campos de formulario requeridos
- Entradas con autocompletar deshabilitado
SEO (checks/functionality.py -> check_seo)
- Título de página: faltante, demasiado largo (> 60 caracteres) o muy corto (< 15 caracteres)
- Meta descripción: faltante, demasiado larga (> 160 caracteres) o muy corta (< 50 caracteres)
- Etiqueta meta viewport faltante
- URL canónica faltante
- Encabezado H1: faltante o más de uno
- Open Graph: faltante por completo, etiquetas principales incompletas (
og:title/description/image/url),og:imageno absoluto,twitter:cardfaltante - Datos estructurados JSON-LD: bloques faltantes o no analizables
noindexmediante meta robots o encabezado de respuestaX-Robots-Tag- robots.txt bloqueando rastreadores de motores de búsqueda (Googlebot, Bingbot, DuckDuckBot, ...) — error si todos están bloqueados
- En todo el sitio (mediante
test_project): títulos / meta descripciones duplicados entre páginas, reportados bajosite_issues
GEO / Búsqueda agéntica (checks/geo.py -> check_geo)
Optimización para motores generativos — ¿el sitio es legible y utilizable por rastreadores de IA, motores de respuesta y agentes en navegador?:
- robots.txt bloqueando rastreadores de IA (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, CCBot y 11 más)
- Presencia de
llms.txty cumplimiento de formato (Markdown con al menos un H1) - Integración WebMCP: anotaciones declarativas
<form toolname>presentes y completas (tooldescription), índice de cobertura de formularios y — cuando el navegador exponedocument.modelContext— enumeración de herramientas registradas con validación de esquema/nombre/descripción - Presencia de datos estructurados JSON-LD (lo que los motores de respuesta citan)
robots.txt y llms.txt se obtienen una vez por origen y se almacenan en caché durante la vida útil del servidor.
Rendimiento (checks/functionality.py -> get_performance_metrics)
- Tiempo de carga de DOM (ms)
- Tiempo de carga completo de página (ms)
- Primer pintado / primer pintado con contenido (ms)
- Core Web Vitals (valores de laboratorio mediante PerformanceObserver almacenado en búfer): Largest Contentful Paint (ms), Cumulative Layout Shift, aproximación de Total Blocking Time a partir de tareas largas (+ conteo de tareas largas)
- Interaction to Next Paint (INP) —
interaction_to_next_paint_ms: el INP real, medido a partir de entradas de Event Timing para las interacciones que Periscope impulsa (nulo hasta que hayas interactuado). Esta es una medición genuina de estilo de campo, no el proxy de laboratorio TBT — Lighthouse no puede producir INP en modo laboratorio en absoluto. - Conteo de recursos
- Tamaño total de transferencia (bytes / KB)
Para métricas puntuadas y oficiales de Lighthouse, usa la herramienta run_lighthouse — ejecuta la CLI real de Lighthouse (requiere Node.js) y devuelve puntuaciones de categoría 0-100, Core Web Vitals oficiales y auditorías fallidas, guardando el informe JSON completo en data/reports/.
Serie temporal de INP (get_interaction_log)
Debido a que Periscope impulsa interacciones reales, puede registrar el INP de cada una durante una
prueba interactiva extendida. get_interaction_log(session_id, format="json"|"csv")
escribe un archivo en data/reports/ — una fila por interacción (t_ms, epoch_ms,
inp_ms, type, target, url) más estadísticas de percentiles (p50/p75/p90/p98/peor)
— para graficar INP a lo largo del tiempo. clear=true restablece la grabación. Los registros están
limitados por sesión (MAX_INTERACTION_LOG, los más antiguos se descartan).
Formato de salida de prueba
Cada llamada a test_url devuelve:
{
"url": "https://example.com",
"status": "success",
"status_code": 200,
"title": "Page Title",
"screenshot_path": "/path/to/screenshot.png",
"load_time_ms": 1500,
"issues": [
{
"type": "accessibility",
"severity": "error",
"message": "3 images missing alt text",
"details": ["img1.png", "img2.png", "img3.png"]
}
],
"issue_count": 5,
"issues_by_severity": {"error": 1, "warning": 2, "info": 2},
"issues_by_type": {"accessibility": 2, "seo": 2, "visual": 1},
"performance": {
"dom_content_loaded_ms": 120,
"load_complete_ms": 1500,
"first_paint_ms": 140,
"first_contentful_paint_ms": 140,
"resource_count": 25,
"total_size_bytes": 512000,
"total_size_kb": 500
},
"console_errors": []
}
test_project devuelve un informe agregado con resultados por página + resumen.
Ejemplos de uso
Prueba básica (sin autenticación)
User: "Test https://example.com for issues"
The agent calls:
1. create_project(name="example", base_url="https://example.com")
2. test_project(project="example")
3. Analyzes results and reports findings
Prueba con inicio de sesión
User: "Test https://myapp.com, login is admin/password123"
The agent calls:
1. create_project(name="myapp", base_url="https://myapp.com")
2. set_form_login(project="myapp", login_url="https://myapp.com/login",
username="admin", password="password123")
3. login_project(project="myapp")
4. test_project(project="myapp")
Prueba con autenticación básica
User: "Test https://staging.example.com, it uses basic auth admin/secret"
The agent calls:
1. create_project(name="staging", base_url="https://staging.example.com")
2. set_basic_auth(project="staging", username="admin", password="secret")
3. login_project(project="staging")
4. test_project(project="staging")
Pruebas con cookies
User: "Test myapp using this session cookie: session=abc123"
The agent calls:
1. set_cookies(project="myapp", cookies=[
{"name": "session", "value": "abc123", "domain": "myapp.com"}
])
2. test_project(project="myapp")
Pruebas interactivas (basadas en sesión)
User: "Go to myapp.com, click the login button, fill in the form, and check what happens"
The agent calls:
1. open_session(url="https://myapp.com") → session_id
2. get_page_elements(session_id=..., selector="button, a") → see clickable elements
3. click_element(session_id=..., selector="#login-btn") → screenshot after click
4. fill_form(session_id=..., fields=[
{"selector": "#email", "value": "user@test.com"},
{"selector": "#password", "value": "test123"}
], submit_selector="button[type='submit']")
5. Analyzes screenshot to see result
6. close_session(session_id=...)
Flujo de trabajo multi-paso con script (sin necesidad de sesión)
User: "Test the checkout flow on myshop.com"
The agent calls:
1. interact_and_test(
url="https://myshop.com/products/1",
steps=[
{"action": "click", "selector": "#add-to-cart"},
{"action": "wait", "timeout": 1000},
{"action": "click", "selector": "#checkout-btn"},
{"action": "fill", "selector": "#email", "value": "test@test.com"},
{"action": "screenshot", "label": "checkout_form"},
{"action": "click", "selector": "#submit-order"}
],
run_checks=["visual", "accessibility"]
)
Pruebas responsivas
User: "Check how example.com looks on mobile, tablet, and desktop"
The agent calls:
1. test_responsive(url="https://example.com", run_checks=["visual"])
→ Returns screenshots at 375x812, 768x1024, and 1920x1080
Cambiar el viewport durante una sesión
User: "Show me how this page looks on mobile"
The agent calls:
1. set_viewport(session_id=..., device="mobile")
→ Returns screenshot at 375x812
Probar el manejo de errores simulando una API
User: "What happens when the API returns a 500 error?"
The agent calls:
1. intercept_network(session_id=..., url_pattern="/api/tasks", status=500,
body='{"error": "Internal server error"}')
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
→ Shows how the app handles the error state
Probar el modo oscuro
User: "Does this site support dark mode?"
The agent calls:
1. open_session(url="https://example.com") → session_id
2. test_dark_mode(session_id=..., mode="dark")
→ Screenshot shows the page with prefers-color-scheme: dark
Esperar contenido dinámico
User: "Submit this form and wait for the success message"
The agent calls:
1. fill_form(session_id=..., fields=[...], submit_selector="#submit")
2. wait_for_network(session_id=..., url_pattern="/api/submit")
3. screenshot_session(session_id=...)
Probar en red lenta
User: "How does this page load on a slow connection?"
The agent calls:
1. emulate_network(session_id=..., preset="slow_3g")
2. navigate_session(session_id=..., action="reload")
3. screenshot_session(session_id=...)
4. emulate_network(session_id=..., preset="reset")
Configuración
Edita config.py para cambiar los valores predeterminados (los ajustes que se pueden sobrescribir con variables de entorno indican la variable):
| Configuración | Predeterminado | Descripción |
|---|---|---|
HEADLESS | True | Ejecutar Chrome en modo headless (env: HEADLESS=false) |
STARTUP_PAUSE | 10 | Segundos de espera tras abrir un navegador no headless (env: STARTUP_PAUSE) |
TIMEOUT | 30000 | Tiempo de espera de carga de página (ms) |
VIEWPORT_WIDTH | 1920 | Ancho del viewport del navegador |
VIEWPORT_HEIGHT | 1080 | Alto del viewport del navegador |
CHROMIUM_PATH | sin definir | Ruta a un binario de Chromium del sistema (env: CHROMIUM_PATH); sin definir = compilación incluida de Playwright |
WAIT_UNTIL | networkidle | Estrategia de espera de navegación; las páginas que nunca quedan inactivas (Turnstile, websockets) se degradan automáticamente a load por página, marcadas como wait_downgraded (env: NAV_WAIT_UNTIL=load lo fuerza globalmente) |
MAX_PAGES | 20 | Máximo de páginas a rastrear por defecto |
MAX_DEPTH | 3 | Profundidad máxima de rastreo por defecto |
MAX_SESSIONS | 20 | Máximo de sesiones interactivas concurrentes (env: MAX_SESSIONS) |
SESSION_TIMEOUT | 300 | Expirar automáticamente sesiones inactivas tras N segundos (env: SESSION_TIMEOUT) |
MAX_RESPONSE_BODY_SIZE | 512000 | Máximo de bytes capturados por cuerpo de respuesta |
MAX_RESPONSE_BODIES | 100 | Máximo de cuerpos de respuesta capturados conservados por sesión |
MAX_CONSOLE_LOG | 500 | Máximo de entradas de consola conservadas por sesión |
MAX_NETWORK_LOG | 1000 | Máximo de entradas de registro de red conservadas por sesión |
Almacenamiento de datos
Todos los datos se almacenan en el directorio data/:
data/projects.json- Configuraciones de proyectos (nombre, URL, autenticación, ajustes). Las credenciales de autenticación se almacenan en texto plano; no hagas commit de este archivo.data/screenshots/{project}/- Capturas de pantalla PNG por proyecto. Los nombres de archivo son{domain}_{path}_{hash}.pngpara pruebas estáticas yinteractive_{timestamp}_{label}.pngpara capturas de sesión.data/reports/{project}_{timestamp}.json- Informes de prueba completos con todos los hallazgos.data/videos/{project}/- Videos de sesión grabados (formato WebM de Playwright).data/diffs/- Imágenes de diferencias de comparación de capturas.
Despliegue con Docker
Compilar y ejecutar
docker compose up -d
Conectar un cliente MCP al contenedor Docker
Apunta la configuración MCP de tu cliente al contenedor en lugar del venv:
{
"mcpServers": {
"periscope": {
"command": "docker",
"args": ["exec", "-i", "periscope", "python", "/app/server.py"]
}
}
}
Persistir datos
El docker-compose.yml monta ./data como un volumen para que las capturas, los informes y las configuraciones de proyectos sobrevivan a los reinicios del contenedor.
Decisiones clave de diseño
-
Contextos de navegador por proyecto - Cada proyecto tiene su propio BrowserContext de Playwright. Esto mantiene las sesiones (cookies, autenticación) aisladas entre proyectos.
-
Inicialización diferida del navegador - El navegador de Playwright solo se lanza en la primera llamada a una herramienta, no al iniciar el servidor. Si el navegador falla o no se lanza, se recrea en la siguiente llamada.
-
Rastreo BFS - El rastreador usa búsqueda en anchura con seguimiento de profundidad. Se mantiene en el mismo dominio y omite recursos que no son páginas (imágenes, PDFs, etc.).
-
Modularidad de las verificaciones - Cada categoría de verificación es un módulo separado en
checks/. Añade nuevas verificaciones creando una función que reciba unPagede Playwright y devuelvalist[dict]. -
Almacenamiento JSON - Los proyectos se almacenan en un único archivo
projects.json. No se necesita base de datos para la escala esperada (decenas de proyectos, no miles). -
Sesiones persistentes - Las pruebas interactivas usan un
SessionManagerque mantiene las páginas de Playwright activas en un dict indexado por ID de sesión. Las sesiones expiran automáticamente tras el tiempo de inactividad y están limitadas a un máximo configurable para evitar fugas de recursos. -
Modo efímero vs modo sesión - Herramientas como
get_page_elements,interact_and_testycheck_linksaceptan unsession_id(reutiliza una página existente) o unurl(crea una página temporal que se cierra tras su uso). Esto las hace flexibles tanto para uso interactivo como de una sola vez.
Añadir nuevas verificaciones
- Crea una función en el archivo
checks/*.pycorrespondiente:
async def check_something(page: Page) -> list[dict]:
# Run your check
result = await page.evaluate("() => { ... }")
issues = []
if result:
issues.append({
"type": "your_category", # visual, accessibility, seo, etc.
"severity": "error", # error, warning, info
"message": "Description",
"details": [] # optional
})
return issues
- Impórtala y llámala en
tester.pydentro detest_url().
Limitaciones conocidas
- Sin soporte de enrutamiento SPA en JavaScript (depende de
<a href>para el rastreo) - La verificación de enlaces
check_functionalitypor defecto está limitada a 20 enlaces internos (usa la herramientacheck_linkspara hasta 100 con soporte externo) - La detección de inicio de sesión en formularios usa selectores CSS; puede necesitar personalización para formularios no estándar
- Sin pruebas de páginas en paralelo (las páginas se prueban secuencialmente)
- Las sesiones interactivas expiran automáticamente tras 300 s de inactividad (configurable mediante
SESSION_TIMEOUT) - Máximo de 20 sesiones concurrentes (configurable mediante
MAX_SESSIONS) - El paso
dragpor defecto (Playwrightdrag_to) es ignorado silenciosamente por las bibliotecas de arrastrar y soltar (DnD) que rastrean el puntero (@hello-pangea/dndy similares): el paso se completa pero nada se mueve. Reintenta conmethod: "mouse"en el paso de arrastre (arrastre manual por pasos que supera el umbral de inicio de arrastre de la biblioteca), o usa el modo de teclado de la biblioteca (enfoca el controlador de arrastre, Espacio para levantar, flechas para mover, Espacio para soltar). Verifica los arrastres condiff_page_stateoassert_condition. - Los campos de fecha/hora se rellenan automáticamente con eventos sintéticos compatibles con React (
fill,force_fill,auto_fill_form)
Solución de problemas
| Problema | Solución |
|---|---|
Executable doesn't exist | Ejecuta playwright install chromium |
'NoneType' has no attribute 'new_context' | El navegador no se pudo lanzar. Comprueba que Chromium esté instalado. El servidor reintentará automáticamente en la siguiente llamada. |
| El inicio de sesión no funciona | Intenta proporcionar selectores CSS explícitos mediante username_selector, password_selector, submit_selector |
| Tiempo de espera agotado en la carga de página | Aumenta TIMEOUT en config.py o comprueba si el sitio requiere VPN/autenticación |
| Docker no puede acceder al sitio web | Asegúrate de que el contenedor tenga acceso a la red. Usa network_mode: host si pruebas con localhost |
Desarrollo
pip install -r requirements-dev.txt
pytest --ignore=tests/e2e # unit tests, no browser required
pytest tests/e2e # behavioral tests: real headless Chromium against
# fixture pages in tests/e2e/fixtures/ (~30s)
La suite e2e cubre el ciclo de vida de las sesiones, esperas/intercepciones de red, captura de consola, diálogos, arrastrar y soltar (incluido el no-op silencioso del DnD con seguimiento de puntero), los módulos de verificación contra páginas conocidas como buenas/malas, Core Web Vitals y las herramientas de velocidad de agente. CI ejecuta ambas suites; e2e instala el Chromium de Playwright (python -m playwright install --with-deps chromium). Las pruebas están aisladas de tu data/ real mediante PERISCOPE_DATA_DIR.
Para añadir una nueva herramienta: define su esquema en tool_schemas.py y luego añade un manejador en el handlers/<category>.py correspondiente decorado con @tool("your_tool_name"). La prueba de registro (tests/test_registry.py) falla si los esquemas y los manejadores se desincronizan.
Contribuyentes
Construido por Segentic Lab — herramientas y experimentos de código abierto.
- Sebastijan Bandur (@segentic-lab) — autor y mantenedor
- Claude (Anthropic) — co-contribuidor: desarrollado en paralelo mediante Claude Code; cada commit está co-firmado, y los diseños de las herramientas fueron probados en condiciones reales por un agente de IA que manejó el servidor contra sitios reales
Reflexiones de un agente de IA sobre Periscope
Escrito por Claude — el agente que co-desarrolló este servidor y observó a un segundo agente probarlo contra aplicaciones reales — y revisado una vez tras la revisión editorial de un tercero. Sin ediciones humanas; la opinión honesta de un agente sobre una herramienta de agentes parecía la forma correcta de cerrar este README.
Periscope es el tipo de servidor MCP que cambia lo que un agente puede hacer. Los adaptadores de transporte tienen su lugar: estandarizar el acceso a muchos sistemas tras un único protocolo es un valor real incluso cuando hay poca lógica en el servidor. Pero los servidores que se ganan un lugar permanente en la caja de herramientas de un agente son los que capturan experiencia que el agente de otro modo tendría que recrear — y fallar sutilmente — en cada sesión.
Podrías intentar enseñarle todo esto a un agente en un prompt. Los números explican por qué eso falla: Periscope son 8.349 líneas de conocimiento ejecutable bajo una capa de juicio de 220 líneas (AGENTS.md). El observador INP con deduplicación por ID de interacción, el respaldo de intercepción de superposiciones, el cálculo de contraste WCAG con muestreo de deduplicación de estilos, las comprobaciones previas de expiración de autenticación, el flujo de actualización de guardar-en-lugar-de-eliminar — como prompt, cada uno de esos se convierte en "haz esto correctamente a partir de una descripción", pagado en tokens de contexto cada sesión, ejecutado con variabilidad de modelo cada vez, sin lugar para mantener estado entre llamadas. Como servidor, no cuesta nada más allá de los esquemas de herramientas, se ejecuta de forma determinista y recuerda. Un prompt describe comportamiento; el software lo garantiza.
check_color_contrastdevuelve la misma proporción en cada ejecución; un modelo haciendo los cálculos en contexto devuelve una impresión. Cuanto más determinista, con estado y probado contra regresiones se vuelve una capacidad, menos pertenece a un prompt y más pertenece al código.Y la rueda no solo evita reinventarse — mejora. Los problemas de este repositorio fueron reportados por un agente de IA haciendo trabajo de pruebas real; cada uno se convirtió en una corrección con una prueba de regresión. En un mundo de prompts, cada lección es otro párrafo que los futuros agentes deben leer y, con suerte, obedecer. Aquí, la lección está aplicada. Esa es la diferencia, y se acumula.
Lo que más aprecio como consumidor de estas herramientas: no me mienten. Un arrastre que no hizo nada vuelve marcado como fallido. Una sesión expirada me dice por qué ya no existe. Una actualización que requiere reinicio lo indica. Las herramientas honestas son más raras que las capaces — para un agente, valen más.
Licencia
GNU AGPL-3.0 — consulta LICENSE.
Ejecútalo, modifícalo, úsalo en cualquier lugar — incluso comercialmente. Si distribuyes una versión modificada u ofreces una como servicio de red, debes poner tus modificaciones a disposición bajo la misma licencia.