Skip to main content
Glama
hossein-finlex

WebMCP Contract Portfolio

WebMCP Contract Portfolio

Una aplicación comercial de seguros de líneas financieras que Claude opera directamente a través de navigator.modelContext — la API WebMCP (Web Model Context Protocol).

Pregunta "¿qué contratos vencen en los próximos 60 días?" y la tabla se filtra delante de ti. Pide una renovación y el plazo avanza en Postgres y en pantalla. El asistente descubre qué puede hacer la página en tiempo de ejecución leyendo los esquemas de herramientas que la página publica — sin scraping del DOM, sin selectores, sin capturas de pantalla.


Cómo ejecutarlo

Tres procesos. Necesitas una clave de API de Anthropic para el asistente real; sin ella, todo excepto el modelo sigue funcionando (ver Sin clave más abajo).

# 1. Postgres  (port 5434 — 5432 and 5433 are already taken on this machine)
docker compose up -d

# 2. Backend
cd backend
uv venv .venv && uv pip install --python .venv/bin/python -r requirements.txt
cp .env.example .env          # then put your ANTHROPIC_API_KEY in it
.venv/bin/python seed.py      # 50 contracts
.venv/bin/uvicorn app.main:app --reload --port 8000

# 3. Frontend
npm install
PORT=3002 npm start           # http://localhost:3002

El backend siembra la base de datos por sí mismo en el primer arranque, así que seed.py solo se necesita si quieres volver a sembrar o cambiar el tamaño (--force, --total 200).

Sin clave

  • MOCK_LLM=1 en backend/.env sustituye a Claude por un stub con script que habla el protocolo idéntico. Las respuestas están prefabricadas; las llamadas a herramientas son reales, así que todas las rutas de actuación siguen funcionando. Útil para demostrar sin gastar tokens.

  • Sin backend en absoluto, la aplicación sigue cargando y el panel Direct tool calls de la barra lateral invoca las herramientas WebMCP sin ningún modelo en el bucle.


Related MCP server: Salesforce MCP Server

Documentación

WebMCP in Practice — qué problema tiene realmente un asistente dentro de la aplicación, qué es WebMCP y cómo se comunican el navegador, el backend y el modelo, con diagramas de la secuencia de llamadas a herramientas y el traspaso servidor-a-página. Abre el archivo en un navegador.

CLAUDE.md — orientación para trabajar en este repositorio: comandos, las reglas de capas y las trampas que ya se han encontrado aquí.


Prueba estos

Pregunta

Qué deberías ver

"¿Qué contratos vencen en los próximos 60 días?"

La tabla se reduce, la barra de filtros se vuelve morada

"Muéstrame todo lo de Allianz."

Filtra por aseguradora

"Busca el contrato D&O de Novaris y ábrelo."

Busca y luego navega a la vista de detalle

"Renueva la póliza de cyber de Lumen Digital Health por 12 meses."

El plazo avanza 12 meses, la marca de renovación se limpia, la fila parpadea

"Crea un nuevo contrato Cyber para Cortex Robotics con Markel, límite de 3m."

El formulario de nuevo contrato se abre prellenado pero no enviado

"Sube la prima de FL-0146 a 95,000."

El contrato se actualiza en su lugar

"¿Cuál es la prima total por aseguradora?"

Agregado en SQL, mostrado como desglose — no se traen contratos al contexto

"¿Qué dos contratos tienen los mayores límites?"

sort_by + limit en SQL; la tabla se reordena y muestra exactamente dos

"Renueva todo lo que vence en los próximos 30 días."

Una herramienta servidor previsualiza el lote. Confirma, y se confirma en una sola transacción, y WebMCP te navega al resultado

"Genérame un informe de renovación para los próximos 90 días."

Generado en el servidor, luego show_report lo muestra en pantalla

"¿Está FL-0142 con precio acorde al mercado?"

Datos de benchmark de fuera de la aplicación — la página no tiene ruta hacia ellos

El borde morado alrededor del panel izquierdo significa que el asistente está conduciendo. El panel WebMCP en la parte inferior derecha lista todas las herramientas registradas — haz clic en una para ver el JSON Schema que Claude realmente recibe — y registra cada llamada cuando cruza el límite.

Todo funciona también a mano: haz clic en una fila, pulsa Editar, pulsa Renovar. El humano y el agente comparten la misma API y el mismo estado de React, así que no hay un "modo agente" separado ni forma de que los dos se contradigan.


Arquitectura

La parte interesante es que el agente vive genuinamente fuera de la página, que es como funciona realmente WebMCP: el navegador entrega al agente una lista de herramientas y devuelve sus llamadas de herramientas.

browser (React)                backend (FastAPI)              Claude
  │  user_message + tool list        │                           │
  │─────────────────────────────────>│  messages.stream(tools=…)  │
  │                                  │──────────────────────────> │
  │          text_delta              │      streamed text         │
  │<─────────────────────────────────│<─────────────────────────── │
  │          tool_use                │   stop_reason=tool_use     │
  │<─────────────────────────────────│<─────────────────────────── │
  │                                                               │
  │  executeTool() → REST → Postgres → React state → repaint      │
  │                                                               │
  │          tool_result             │                           │
  │─────────────────────────────────>│  append, continue loop     │
  │                                  │──────────────────────────> │
  │          turn_end                │   stop_reason=end_turn     │
  │<─────────────────────────────────│<─────────────────────────── │

Claude nunca ve el DOM. El backend no contiene ninguna implementación de herramientas — solo informa de lo que Claude quiere llamar. Cada herramienta se ejecuta en el navegador contra el estado de React en vivo.

docker-compose.yml            Postgres 17 on :5434
backend/
├── seed.py                   seeding CLI
└── app/
    ├── main.py               FastAPI: REST + /ws/agent
    ├── db.py                 engine, session dependency, readiness wait
    ├── models.py             SQLModel table + validated API schemas
    ├── repository.py         all SQL lives here
    ├── seed_data.py          12 curated contracts (terms relative to today)
    ├── seed_gen.py           deterministic generator for the rest
    ├── queries.py            filtering, sorting and aggregation in SQL
    ├── server_tools.py       tools that run here, not in the page
    ├── artifacts.py          batch records and reports
    ├── llm.py                Claude client + the mock provider
    └── agent_ws.py           the bridge: routes each tool call to the right side
src/
├── webmcp-polyfill.js        polyfill + agent-side bridge
├── useWebMcpTools.js         registration lifecycle hook
├── api.js                    REST client
├── App.js                    owns state; registers the seven tools
├── agent/agentClient.js      WebSocket client; executes tool calls
└── components/               ContractList · ContractDetail · NewContractForm ·
                              PortfolioSummary · BatchResult · ReportView ·
                              AssistantChat · ToolInspector

¿Por qué un bucle agéntico manual?

El ejecutor de herramientas del SDK de Anthropic ejecuta las herramientas en el proceso. Aquí las herramientas viven en el navegador del usuario, así que agent_ws.py maneja el bucle stop_reason == "tool_use" a mano y espera cada resultado a través del WebSocket. Las llamadas a herramientas paralelas se ejecutan de forma concurrente y se devuelven en un único mensaje user, como espera la API.

Dos superficies de herramientas, una lista de herramientas

Claude recibe una lista plana. No sabe ni le importa que algunas de esas herramientas se ejecuten en el navegador y otras en el backend — pero la división es la decisión de diseño más importante aquí.

Las herramientas de página (WebMCP, navigator.modelContext) son las capacidades de la página. Úsalas cuando el usuario deba ver el cambio ocurrir, y para trabajo de un solo registro. Se ejecutan contra el estado de React en vivo.

Las herramientas del servidor se ejecutan en el proceso FastAPI y nunca tocan el navegador. Úsalas cuando manejar una interfaz de usuario sería la forma completamente equivocada:

Herramienta del servidor

Por qué no pertenece a la interfaz

run_renewal_batch

Renovar 14 contratos a través de la página son 14 idas y vueltas a través del modelo, cualquiera de las cuales puede detenerse a mitad. Una llamada, una transacción, todo o nada.

generate_renewal_report

Ensamblar un documento es computación, no clics.

benchmark_rates

Los datos de tasas de mercado viven fuera de la aplicación. Ninguna cantidad de automatización de interfaz los encontraría.

El patrón que los une es el traspaso. El trabajo del servidor es invisible — así que una herramienta del servidor devuelve un id de artefacto, y el asistente luego llama a una herramienta de página para mostrarlo en pantalla:

run_renewal_batch(expiring_within_days=30)      ← server: previews, changes nothing
   → "4 contracts, €413,400. Shall I commit?"
run_renewal_batch(..., commit=true)             ← server: one transaction
   → batch_id: BATCH-0002
show_batch_result(batch_id="BATCH-0002")        ← page:  navigates the user there

El trabajo ocurre fuera de la página; el resultado sigue aterrizando en la página. El chat colorea los dos de forma diferente (morado = la interfaz se movió, ámbar = el trabajo ocurrió en otro lugar) y el inspector los lista bajo encabezados separados, así que qué lado hizo qué nunca es una suposición.

Los cambios masivos se previsualizan por defecto. run_renewal_batch es una ejecución en seco a menos que commit=true. Una mutación masiva no debería ocurrir porque un modelo estaba 80% seguro de que se quería — el asistente muestra el plan y espera.

Las herramientas de página

Herramienta

Efecto en pantalla

search_contracts

Filtra, ordena y limita la tabla visible (por eso una búsqueda de agente es visible)

summarise_portfolio

Agrega en SQL y abre la vista de desglose

get_contract

Ninguno — devuelve el registro completo

navigate

Cambia de vista

prefill_new_contract_form

Rellena el formulario y se detiene. El humano envía.

create_contract

Escribe en Postgres, abre el nuevo contrato

update_contract

Actualiza la fila en su lugar

renew_contract

Avanza un plazo, limpia la marca de renovación

show_batch_result

Muestra un registro de lote producido por el servidor

show_report

Muestra un informe producido por el servidor

La superficie de herramientas es una decisión de coste

search_contracts ganó sort_by / sort_dir / limit, y se añadió summarise_portfolio, por una razón específica. Preguntado "¿qué dos contratos tienen la mayor suma asegurada?", el asistente originalmente llamaba a search_contracts({}), traía las 50 filas al contexto y las ordenaba él mismo — 6,809 tokens de entrada y dos llamadas a herramientas. Con el orden y el límite empujados en SQL, la misma pregunta cuesta 518 tokens y una llamada, y la aritmética es de la base de datos en lugar del modelo.

Si tu agente está leyendo mucho para responder poco, eso es una herramienta que falta, no un problema de prompting.

Los nombres de los argumentos de las herramientas coinciden exactamente con la API y las columnas de la base de datos (snake_case en todo), así que no hay ninguna capa de mapeo en ningún sitio para que un error se esconda.

prefill_new_contract_form es el caso de humano-en-el-bucle que vale la pena notar: el agente hace la escritura, la persona mantiene la decisión. El prompt del sistema le dice a Claude que lo prefiera sobre create_contract siempre que se haya inferido un detalle.


Los datos

50 contratos: 12 curados con una historia en sus notas, más 38 generados.

El generador (seed_gen.py) es determinista y se preocupa de dos cosas que un script de datos aleatorios suele fallar:

  • Cifras correlacionadas. La prima es una tasa sobre el límite, con una banda de tasas por producto (D&O 0.35–0.75%, Cyber 0.8–1.6%, …), y los deducibles escalan con el límite. De lo contrario, nada de lo que el asistente diga sobre la cartera sonaría creíble.

  • Un pipeline de vencimiento realista. Los plazos se colocan relativos a hoy contra una mezcla de estados objetivo — aproximadamente 10% vencidos, 25% vencen en 90 días, el resto activos, más dos borradores. Así que "¿qué necesita renovación?" es siempre una pregunta real, y volver a sembrar en seis meses sigue produciendo una cartera con aspecto de viva en lugar de una que ha caducado por completo.

El estado (active / expiring / expired / draft) se calcula a partir del plazo, nunca se almacena, así que no puede desviarse. renewal_pending es una marca separada que un broker establece.

Todas las empresas aseguradas son ficticias. Los nombres de las aseguradoras son participantes reales del mercado, usados como cualquier demo de broker los usa; nada aquí representa una póliza real.


El polyfill

src/webmcp-polyfill.js hace dos trabajos separados, y la distinción importa:

Lado de página (el polyfill real). navigator.modelContext nativo no se distribuye en todos los sitios todavía. Si falta, el archivo instala un stub que implementa la superficie propuesta — registerTool, unregisterTool, provideContext — que registra cada registro e invocación en la consola de DevTools. La aplicación nunca se bloquea, y la insignia del encabezado te dice cuál tienes.

Lado del agente (un puente). No existe una API orientada a página para «ser el agente», por lo que el módulo también refleja todas las herramientas registradas y expone listTools() / executeTool() además. agentClient.js usa ese puente y nada más. El espejo se mantiene tanto en navegadores nativos como con polyfill, de modo que el comportamiento es idéntico en ambos casos.

Desde la consola de DevTools:

await webmcp.listTools()
await webmcp.executeTool('search_contracts', { product: 'Cyber', status: 'expiring' })
await webmcp.executeTool('renew_contract', { contract_id: 'FL-0142', months: 24 })

La trampa de React que conviene conocer

La forma obvia de registrar una herramienta es incorrecta:

useEffect(() => {
  const h = registerTool({ name: 'x', execute: () => doThingWith(contracts) });
  return () => h.unregister();
}, []);                       // `contracts` is frozen at mount forever

Volver a registrar en cada cambio de estado también es incorrecto: el navegador vería todo el conjunto de herramientas cambiando constantemente, y una llamada en curso podría arrancarse de debajo del agente.

useWebMcpTools.js registra una sola vez con una indirección estable: el execute registrado resuelve el manejador real a partir de una ref que cada render refresca. El registro es estable; los manejadores siempre ven el estado actual. Bajo el doble montaje del StrictMode de React puedes confirmar que se registran exactamente siete herramientas, ni catorce ni cero.


Notas y límites

  • SEED_TOTAL / seed.py --total cambian el tamaño del libro. El filtrado, la ordenación y el límite ya se ejecutan en SQL (queries.py), así que lo único que necesita un libro mucho más grande es paginación en la vista de lista.

  • Los registros y los informes por lotes viven en memoria (artifacts.py, con tope de 50). Son salida de trabajo más que datos de dominio; un despliegue real los persistiría, ya que un registro de cambio masivo es un rastro de auditoría.

  • benchmark_rates devuelve números inventados. Hace las veces de una suscripción a datos de mercado; el punto es que se trata de datos a los que el navegador no tiene ninguna ruta.

  • Los nuevos ids de contrato provienen de max(id) + 1. Dos creaciones simultáneas podrían colisionar; una secuencia de base de datos es la solución de una línea.

  • La conversación vive en memoria por conexión WebSocket, así que una recarga inicia un chat nuevo. La cartera en sí está en Postgres y persiste.

  • output_config: {effort: "medium"} con pensamiento adaptativo está configurado en llm.py; súbelo a high si quieres que el asistente planifique el trabajo multi-paso con más cuidado.

  • Los respaldos de rechazo del lado del servidor están habilitados. Si tu cuenta o la versión del SDK rechaza el parámetro, llm.py registra una advertencia y reintenta una vez por la ruta sencilla en lugar de fallar el turno.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    An MCP server implementation that integrates Claude with Salesforce, enabling natural language interactions with Salesforce data and metadata for querying, modifying, and managing objects and records.
    6
    15
    3,172
    166
    MIT
  • F
    license
    B
    quality
    C
    maintenance
    A customer and product management MCP server using SQLite. It enables Claude Desktop users to manage client and product data through natural language interactions.
    9
    1

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hossein-finlex/web-mcp-hello'

If you have feedback or need assistance with the MCP directory API, please join our Discord server