Skip to main content
Glama
irgiaryanda

ts-mcp-durable-browser-automation

by irgiaryanda
README.md
# ts-mcp-durable-browser-automation

A production-grade **Model Context Protocol (MCP) Server** built in TypeScript that wraps a resilient Playwright browser engine with an SQLite-backed **Exactly-Once (idempotency) lock**, purpose-built to automate interactions with legacy AEC (Architecture, Engineering, Construction) portals that expose unpredictable, brittle APIs.

---

## Architecture

Three discrete, independently testable layers:

```
LLM / MCP Client
      │
      ▼
┌─────────────────────────────┐
│  Layer 1 · MCP Interface    │  @modelcontextprotocol/sdk · Zod v4 input validation
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│  Layer 2 · Idempotency Lock │  SQLite state machine: PENDING → SUCCESS | FAILED
└────────────┬────────────────┘  Rejects concurrent duplicate requests
             │
             ▼
┌─────────────────────────────┐
│  Layer 3 · Playwright Engine│  Resilient locators · auto-retry · screenshot capture
└─────────────────────────────┘  Guaranteed browser.close() via try/finally
```

---

## Core Features

- **Exactly-Once Execution** — Every tool call requires an `idempotencyKey`. The SQLite lock engine prevents duplicate executions and returns cached results on replay.
- **Race Condition Protection** — Concurrent requests with the same key are rejected while a `PENDING` operation is in flight.
- **Resilient Browser Automation** — Playwright uses `getByRole` / `getByText` locators instead of fragile CSS selectors, with explicit auto-waiting for unpredictable DOM mutations.
- **Zero Browser Leaks** — All browser and context instances are destroyed in `try/finally` blocks, even on fatal crashes.
- **Strict Type Safety** — `strict: true` TypeScript, Zod v4 runtime schema validation, no `any` types.
- **Physical Proof Generation** — A CLI script runs a full end-to-end simulation and outputs a markdown artifact containing structured execution logs and Base64-encoded screenshots.

---

## Project Structure

```
ts-mcp-durable-browser-automation/
├── src/
│   ├── index.ts                 # MCP Server entry point
│   ├── mcp/                     # Zod schemas, tool handlers
│   ├── automation/              # Playwright lifecycle, submitPermit task
│   ├── core/                    # SQLite idempotency lock, structured logger
│   └── types/                   # Shared TypeScript interfaces
├── scripts/
│   └── generate_proof.ts        # CLI — generates automation_proof.md
├── tests/
│   ├── idempotency.test.ts      # Proves exactly-once lock behaviour
│   └── browser.test.ts          # Proves browser lifecycle + form automation
├── docs/
│   └── mock_portal.html         # Offline legacy AEC portal target
└── automation_proof.md          # Generated artifact (Base64 screenshots + logs)
```

---

## Prerequisites

- Node.js ≥ 20
- npm ≥ 10

---

## Setup

```bash
git clone https://github.com/irgiaryanda/ts-mcp-durable-browser-automation.git
cd ts-mcp-durable-browser-automation
npm install
npx playwright install chromium
```

---

## Commands

### Run automated tests

Mathematically proves the idempotency lock (SUCCESS cache hit, PENDING race rejection, FAILED retry) and Playwright browser lifecycle:

```bash
npm test
```

Expected output:

```
Tests:       8 passed, 8 total
```

### Generate physical proof artifact

Runs a full end-to-end simulation against the offline mock AEC portal and writes `automation_proof.md` with execution logs and a Base64 screenshot:

```bash
npm run prove
```

Output file: `automation_proof.md`

### Type-check (zero errors)

```bash
npx tsc --noEmit
```

---

## MCP Tool: `submit_planning_permit`

| Parameter | Type | Description |
|---|---|---|
| `idempotencyKey` | `string` | Unique key guaranteeing exactly-once execution |
| `projectName` | `string` | Name of the construction project |
| `permitType` | `enum` | `new_construction` \| `renovation` \| `demolition` \| `change_of_use` \| `heritage` |
| `applicantId` | `string` | Registered applicant identifier |
| `submitDate` | `string` | Date in `YYYY-MM-DD` format |

The tool is safe to retry — replaying a call with an existing `idempotencyKey` that previously succeeded returns the cached result instantly without re-executing the browser automation.

---

## Key Technical Decisions

| Decision | Rationale |
|---|---|
| SQLite for idempotency store | Zero-dependency, file-based, ACID-compliant. No external service required. |
| Zod v4 for runtime validation | Full compatibility with `@modelcontextprotocol/sdk` type system (`ZodRawShapeCompat`). |
| `getByRole` / `getByText` locators | Immune to CSS class changes in legacy portal markup. |
| `try/finally` around all browser instances | Guarantees `browser.close()` even on unhandled exceptions. |
| Offline `mock_portal.html` as test target | Eliminates network flakiness — deterministic, reproducible automation proof. |