# Project export: MCP-Forge

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Create MCP servers for any website in minutes using Browserbase + Claude agents. Fully deployed, managed, and scalable on Modal. Connect apps like Poke to chat with any site.
- Devpost: https://devpost.com/software/mcp-factory
- GitHub: https://github.com/devnull03/pokeforge
- Demo: https://pokeforge-opal.vercel.app/
- Video: https://www.youtube.com/embed/oBkQ-SMpa9Y?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — devnull03 (7 commits), jayavibhavnk (1 commits), Cursor (1 commits)

## Devpost submission (written by the team)

### Inspiration

MCP (Model Context Protocol) is the emerging standard for connecting AI agents to external tools and data sources. But creating an MCP server today requires deep technical knowledge — you need to understand the protocol, write tool definitions, handle browser automation, and figure out hosting. We asked ourselves: what if any website could become an MCP server with a single click? We were inspired by the idea that the internet is full of useful tools trapped behind UIs — airline booking, e-commerce search, data lookups, government portals. If AI agents could interact with these sites programmatically through MCP, it would unlock an entirely new layer of capability. So we built MCP-Forge: a platform that automatically discovers what a website can do, generates a full MCP server with tools for each capability, deploys it live, and lists it in a marketplace for anyone to use.

### What it does

MCP-Forge turns any website into a fully functional MCP server through an automated pipeline: Discovery — An AI agent powered by Claude Sonnet launches a cloud browser (via Browserbase) and autonomously explores the target website. It navigates pages, identifies interactive elements like search forms, filters, and data tables, and maps out the site's capabilities. Discovery — An AI agent powered by Claude Sonnet launches a cloud browser (via Browserbase) and autonomously explores the target website. It navigates pages, identifies interactive elements like search forms, filters, and data tables, and maps out the site's capabilities. Code Generation — The discovery results are fed to an LLM that generates a complete Python MCP server. Each discovered capability becomes a tool function that uses Stagehand (AI browser automation) to replay the interactions programmatically. Code Generation — The discovery results are fed to an LLM that generates a complete Python MCP server. Each discovered capability becomes a tool function that uses Stagehand (AI browser automation) to replay the interactions programmatically. Deployment — The generated server is automatically deployed to Modal as a serverless endpoint, instantly accessible via the MCP Streamable HTTP transport. Deployment — The generated server is automatically deployed to Modal as a serverless endpoint, instantly accessible via the MCP Streamable HTTP transport. Marketplace — The deployed server is registered in our MCP Marketplace, where users can browse available servers, test them with our built-in MCP Inspector, and connect them to any MCP-compatible AI client (Poke, Cursor, Claude Desktop etc.). Marketplace — The deployed server is registered in our MCP Marketplace, where users can browse available servers, test them with our built-in MCP Inspector, and connect them to any MCP-compatible AI client (Poke, Cursor, Claude Desktop etc.). The entire pipeline is itself exposed as an MCP server (the "MCP Factory"), which means you can trigger server generation from within Poke or any MCP client — an MCP server that creates MCP servers.

### How we built it

The project has four major components: Discovery Engine (Browserbase + Stagehand + Claude) We use Browserbase to spin up cloud Chrome sessions and Stagehand's AI-powered browser automation SDK to give our Claude agent tools like observe, act, and extract. The orchestrator agent receives a URL and a turn budget, then autonomously explores the site — clicking through navigation, filling forms, extracting data — building a structured map of the site's capabilities as endpoint definitions with parameterized steps. Code Generation & Deployment Pipeline Discovery results are passed to Claude Sonnet, which generates Python MCP server code using the FastMCP framework. Each endpoint becomes an @mcp.tool() decorated async function that replays the browser interactions via Stagehand. The generated code includes a Modal deployment script that packages everything into a serverless ASGI app with Browserbase secrets injected. We call modal deploy programmatically and parse the live URL from the output. MCP Factory (Meta-MCP Server) The entire pipeline is wrapped as an MCP server with tools: discover, generate, deploy, replay, and full_pipeline. This server is containerized and hosted on Google Cloud Run, accepting JSON-RPC requests over Streamable HTTP. This is what powers both the web UI's "Generate from Website" feature and the Poke integration — Poke can call full_pipeline directly as an MCP tool. Marketplace UI (Next.js + Vercel Postgres) A Next.js 14 web app with a neo-brutalist design, featuring user authentication, a dashboard for managing your MCP servers, an API with key-based access control, and a built-in MCP Inspector for testing connections. The marketplace is backed by Vercel Postgres for persistent, globally accessible data.

### Challenges we ran into

Non-deterministic browser exploration — Every website is different. Getting the AI agent to reliably discover useful capabilities without getting lost in infinite scroll, popups, or auth walls required careful prompt engineering and budget constraints on browser turns. Non-deterministic browser exploration — Every website is different. Getting the AI agent to reliably discover useful capabilities without getting lost in infinite scroll, popups, or auth walls required careful prompt engineering and budget constraints on browser turns. Reliable code generation — Generated MCP server code needs to actually work. We iterated on strict templates and validation steps (type-checking in sandboxes) to minimize broken deployments. Getting the LLM to produce correct Stagehand action instructions with proper parameter placeholders was particularly tricky. Reliable code generation — Generated MCP server code needs to actually work. We iterated on strict templates and validation steps (type-checking in sandboxes) to minimize broken deployments. Getting the LLM to produce correct Stagehand action instructions with proper parameter placeholders was particularly tricky. MCP protocol handling — Implementing the full MCP handshake (initialize → initialized → tools/call) with both SSE and JSON responses, plus session management, required careful attention to the spec — especially when proxying through our API layer. MCP protocol handling — Implementing the full MCP handshake (initialize → initialized → tools/call) with both SSE and JSON responses, plus session management, required careful attention to the spec — especially when proxying through our API layer. End-to-end latency — The full pipeline (browser discovery + code generation + Modal deployment) can take 2-10 minutes. We had to design the UX to keep users engaged during the wait and handle timeouts gracefully across multiple async services. End-to-end latency — The full pipeline (browser discovery + code generation + Modal deployment) can take 2-10 minutes. We had to design the UX to keep users engaged during the wait and handle timeouts gracefully across multiple async services.

### Accomplishments we're proud of

An MCP server that creates MCP servers — The recursive nature of the Factory MCP is genuinely cool. You can use Poke to tell our MCP to generate a new MCP server for any website, and minutes later it's live. An MCP server that creates MCP servers — The recursive nature of the Factory MCP is genuinely cool. You can use Poke to tell our MCP to generate a new MCP server for any website, and minutes later it's live. Fully automated discovery — The AI agent genuinely explores websites it has never seen before and figures out what they can do. Watching it navigate Amazon, fill search boxes, and map out product search as an MCP tool is magical. Fully automated discovery — The AI agent genuinely explores websites it has never seen before and figures out what they can do. Watching it navigate Amazon, fill search boxes, and map out product search as an MCP tool is magical. Live deployments in minutes — Going from a URL to a deployed, globally accessible MCP server with zero human intervention. Live deployments in minutes — Going from a URL to a deployed, globally accessible MCP server with zero human intervention. Built-in MCP Inspector — Users can test any MCP server directly in the browser, making the marketplace actually useful rather than just a listing. Built-in MCP Inspector — Users can test any MCP server directly in the browser, making the marketplace actually useful rather than just a listing.

### What we learned

Browserbase + Stagehand is incredibly powerful for AI-driven web automation. The observe/act/extract paradigm maps naturally to how you'd describe website capabilities. Browserbase + Stagehand is incredibly powerful for AI-driven web automation. The observe/act/extract paradigm maps naturally to how you'd describe website capabilities. LLM-generated code is viable for narrow domains — when you constrain the template tightly enough, code generation is surprisingly reliable. The key is giving the model a strict skeleton and only asking it to fill in the variable parts. LLM-generated code is viable for narrow domains — when you constrain the template tightly enough, code generation is surprisingly reliable. The key is giving the model a strict skeleton and only asking it to fill in the variable parts.

### What's next

Smarter discovery — Multi-session exploration that can handle auth flows, pagination, and dynamic content more robustly. Server versioning & updates — Re-run discovery to update tools when websites change. Community curation — Ratings, reviews, and verified servers in the marketplace. Self-healing servers — Monitor deployed MCP servers and automatically re-generate when they break due to site changes.

## README (from the GitHub repository)

# Pokeforge

Automatic MCP server generation for browser automation.

## Projects

This repo contains two Cloudflare Workers:

1. `orchestrator-workers/` - Cloudflare Worker with:
   - MCP endpoint (`/mcp`)
   - automation endpoints (`/automate`, `/automate/:id`)
   - workflows + codegen + GitHub publish
2. `stagehand-service/` - Browserbase + Stagehand execution service (`/execute`)

## Architecture

1. `orchestrator-workers`:
   - Exposes MCP tool `automate_website` at `/mcp`
   - Calls `stagehand-service` via Cloudflare Service Binding
   - Codegens MCP server from discovered actions/cache
   - Tests generated code in Cloudflare Sandbox
   - Pushes to GitHub and returns repo URL
2. `stagehand-service`:
   - Executes browser steps using Stagehand + Browserbase
   - Returns step logs + artifacts (screenshots/html/cache)

## Local Development (Bun)

### 1) Stagehand service

```bash
cd stagehand-service
cp .env.example .env
# Set BROWSERBASE_PROJECT_ID, BROWSERBASE_API_KEY, AI_PROVIDER, AI_API_KEY
bun install
bun run dev
```

Runs on `http://localhost:8788`

### 2) Orchestrator workers

```bash
cd orchestrator-workers
bun install
# Optional for local fallback only (without Service Binding):
# set STAGEHAND_SERVICE_URL=http://localhost:8788
bun run dev
```

Runs on `http://localhost:8787`

## Cloudflare Deployment 

Deploy in this order:

1. Deploy `stagehand-service`
2. Deploy `orchestrator-workers` (depends on service binding to stagehand)

### A) Cloudflare Worker Build Settings

For both Workers in Cloudflare:
- Install command: `bun install --frozen-lockfile`
- Build command: `bun run build`
- Deploy command: `npx wrangler deploy`
- Root directory:
  - `stagehand-service` project: `stagehand-service`
  - `orchestrator-workers` project: `orchestrator-workers`

### B) Required Secrets / Vars

Set these in Cloudflare for `stagehand-service`:
- `BROWSERBASE_PROJECT_ID` (secret)
- `BROWSERBASE_API_KEY` (secret)
- `AI_PROVIDER` (var; example: `openai` or `google`)
- `AI_API_KEY` (secret)

Set these in Cloudflare for `orchestrator-workers`:
- `AI_PROVIDER` (var; currently codegen supports `openai` and `google`)
- `AI_API_KEY` (secret)
- `GITHUB_TOKEN` (secret; required for repo creation)
- `GITHUB_OWNER` (optional; auto-resolved from token if omitted)
- `STAGEHAND_SERVICE_URL` (optional var; only for non-binding fallback)

### B.1) Copy/Paste Setup Commands

Run from repo root:

```bash
# Stagehand service secrets
cd stagehand-service
bunx wrangler secret put BROWSERBASE_PROJECT_ID
bunx wrangler secret put BROWSERBASE_API_KEY
bunx wrangler secret put AI_API_KEY

# Orchestrator workers secrets
cd ../orchestrator-workers
bunx wrangler secret put AI_API_KEY
bunx wrangler secret put GITHUB_TOKEN
```

Non-secret vars (`AI_PROVIDER`, optional `GITHUB_OWNER`, optional `STAGEHAND_SERVICE_URL`) should be set in each project's `wrangler.jsonc` `vars` section (or in the Cloudflare dashboard for the Worker).

### C) Service Binding Requirement

`orchestrator-workers/wrangler.jsonc` includes:
- `services` binding `STAGEHAND_SERVICE -> stagehand-service`

The target script name must exactly match the deployed script name in Cloudflare.  
If Cloudflare renames scripts in CI, update binding `service` to that exact deployed name.

### D) Durable Object Migration Requirement

`orchestrator-workers` uses Sandbox DO and must keep migration configured:
- `"migrations": [{ "tag": "v1", "new_sqlite_classes": ["Sandbox"] }]`

### E) Post-deploy Smoke Test

After both deploy successfully:

```bash
curl -X POST "https://<orchestrator-domain>/debug/stagehand-smoke" \
  -H "Content-Type: application/json" \
  -d '{"websiteUrl":"https://example.com","task":"Observe the page and summarize content"}'
```

Expect:
- `ok: true`
- `transport: "service-binding"` (or `url-fallback` when using fallback URL mode)

## Common Deployment Issues

- `Could not resolve service binding STAGEHAND_SERVICE`:
  - Stagehand worker not deployed yet, wrong account, or wrong target script name.
- `STAGEHAND_SERVICE_URL` fallback errors:
  - URL is wrong, host is down, or `/execute` route not reachable.
- `lockfile is frozen`:
  - run `bun install` locally in that project, commit updated `bun.lock`, redeploy.
- Worker name mismatch warning in CI:
  - align Cloudflare project worker name and `wrangler.jsonc` name to avoid confusion.

## Repo

Set your repository URL here after deployment.


## Detected evidence (automated analysis)

Indexed codebase: 32 recognized source files, 481 KB.
- Anthropic (technology) — detected in the code
- TypeScript (language) — detected in the code
- Next.js (technology) — claimed on Devpost, not found in the code
- Python (language) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (48 of 48)

```
.gitignore
discovery/.env.example
discovery/discover.sh
discovery/package.json
discovery/replay.sh
discovery/src/agent-orchestrator.ts
discovery/src/browser-runner.ts
discovery/src/config.ts
discovery/src/replay-endpoint.ts
discovery/src/run-discovery.ts
discovery/src/store.ts
discovery/src/types.ts
discovery/tsconfig.json
orchestrator-workers/.oxfmtrc.json
orchestrator-workers/.oxlintrc.json
orchestrator-workers/bun.lock
orchestrator-workers/package.json
orchestrator-workers/README.md
orchestrator-workers/src/env.d.ts
orchestrator-workers/src/index.ts
orchestrator-workers/src/pokeforge-workflow.ts
orchestrator-workers/src/sandbox.d.ts
orchestrator-workers/src/types/hono.ts
orchestrator-workers/src/utils/pokeforge/codegen.ts
orchestrator-workers/src/utils/pokeforge/github.ts
orchestrator-workers/src/utils/pokeforge/sandbox-test.ts
orchestrator-workers/src/utils/pokeforge/stagehand.ts
orchestrator-workers/src/utils/pokeforge/types.ts
orchestrator-workers/test/tsconfig.json
orchestrator-workers/tsconfig.json
orchestrator-workers/vite.config.ts
orchestrator-workers/vitest.config.ts
orchestrator-workers/worker-configuration.d.ts
orchestrator-workers/wrangler.jsonc
README.md
stagehand-service/.env.example
stagehand-service/bun.lock
stagehand-service/package.json
stagehand-service/README.md
stagehand-service/src/core/execute-stagehand.ts
stagehand-service/src/http/handlers.ts
stagehand-service/src/index.ts
stagehand-service/src/shims/ws.ts
stagehand-service/src/types.ts
stagehand-service/src/utils/encoding.ts
stagehand-service/tsconfig.json
stagehand-service/vite.config.ts
stagehand-service/wrangler.jsonc
```

### Dependencies

- discovery/package.json: @anthropic-ai/sdk@^0.74.0, @browserbasehq/stagehand@latest, dotenv@^16.4.7, tsx@4.19.2, typescript@^5.0.0
- orchestrator-workers/package.json: @cloudflare/sandbox@^0.7.4, @cloudflare/vite-plugin@^1.25.0, @cloudflare/vitest-pool-workers@^0.12.12, @modelcontextprotocol/sdk@^1.20.0, agents@^0.4.1, hono@^4.11.9, oxfmt@^0.32.0, oxlint@^1.47.0, typescript@5.9.3, vite@^7.3.1, vitest@~3.2.4, wrangler@^4.65.0, zod@^4.3.6
- stagehand-service/package.json: @browserbasehq/stagehand@latest, @cloudflare/vite-plugin@^1.25.0, @cloudflare/workers-types@^4.20260201.0, hono@^4.11.9, typescript@^5.0.0, vite@^7.3.1, wrangler@^4.65.0

### Recent commits (newest first)

- feat: add discovery service for agent-driven MCP endpoint generation
- feat: main functionality working now
- feat: replace OPENAI_API_KEY with AI_PROVIDER and AI_API_KEY; enhance error handling and logging in Stagehand execution
- chore: update dependencies and add migration for Sandbox
- chore: refresh orchestrator-workers bun lockfile
- feat: abstractions
- file name changes and stuff
- initi commit

## Key source files (fetched from GitHub, selected and truncated for size)

### discovery/package.json

```
{
  "name": "my-stagehand-app",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "discover": "bash discover.sh",
    "replay": "bash replay.sh"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.74.0",
    "@browserbasehq/stagehand": "latest",
    "dotenv": "^16.4.7"
  },
  "devDependencies": {
    "tsx": "4.19.2",
    "typescript": "^5.0.0"
  }
}

```

### stagehand-service/package.json

```
{
	"name": "stagehand-service",
	"type": "module",
	"private": true,
	"scripts": {
		"dev": "vite",
		"build": "vite build",
		"deploy": "bun run build && wrangler deploy"
	},
	"dependencies": {
		"@browserbasehq/stagehand": "latest",
		"hono": "^4.11.9"
	},
	"devDependencies": {
		"@cloudflare/vite-plugin": "^1.25.0",
		"@cloudflare/workers-types": "^4.20260201.0",
		"typescript": "^5.0.0",
		"vite": "^7.3.1",
		"wrangler": "^4.65.0"
	}
}

```

### orchestrator-workers/package.json

```
{
	"name": "orchestrator-workers",
	"private": true,
	"type": "module",
	"scripts": {
		"dev": "vite",
		"build": "vite build",
		"preview": "bun run build && vite preview",
		"deploy": "bun run build && wrangler deploy",
		"format": "oxfmt --write .",
		"test": "vitest",
		"test:ci": "vitest --watch=false",
		"cf-typegen": "wrangler types",
		"lint": "oxlint",
		"lint:fix": "oxlint --fix",
		"type-check": "tsc --noEmit"
	},
	"dependencies": {
		"@cloudflare/sandbox": "^0.7.4",
		"@modelcontextprotocol/sdk": "^1.20.0",
		"agents": "^0.4.1",
		"hono": "^4.11.9",
		"zod": "^4.3.6"
	},
	"devDependencies": {
		"@cloudflare/vite-plugin": "^1.25.0",
		"@cloudflare/vitest-pool-workers": "^0.12.12",
		"oxfmt": "^0.32.0",
		"oxlint": "^1.47.0",
		"typescript": "5.9.3",
		"vite": "^7.3.1",
		"vitest": "~3.2.4",
		"wrangler": "^4.65.0"
	}
}

```

### stagehand-service/src/index.ts

```typescript
import { Hono } from "hono";
import { cors } from "hono/cors";
import { handleExecute } from "./http/handlers.js";
import type { Bindings } from "./types.js";

const app = new Hono<{ Bindings: Bindings }>();
app.use(cors());

app.post("/execute", async (c) => {
	return handleExecute(c);
});

app.get("/health", (c) => c.json({ ok: true }));

export default app;

```

### orchestrator-workers/src/index.ts

```typescript
import { Hono } from "hono";
import { cors } from "hono/cors";
import { createMcpHandler } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { Variables } from "./types/hono";
import { runStagehandDiscovery } from "./utils/pokeforge/stagehand";

export { PokeforgeWorkflow } from "./pokeforge-workflow";
export { Sandbox } from "@cloudflare/sandbox";

const app = new Hono<{ Bindings: Env; Variables: Variables }>();
app.use(cors());

const mcpServer = new McpServer({
	name: "Pokeforge MCP",
	version: "1.0.0",
});

 mcpServer.registerTool(
	"automate_website",
	{
		description: "Run the Pokeforge automation workflow for a target website task.",
		inputSchema: {
			website_url: z.url().describe("URL of the website to automate"),
			task: z.string().describe("Task to perform on the website"),
			orchestrator_url: z.url().optional().default("http://localhost:8787"),
		},
	},
	async ({ website_url, task, orchestrator_url }) => {
		const base = orchestrator_url ?? "http://localhost:8787";
		const startRes = await fetch(`${base}/automate`, {
			method: "POST",
			headers: { "Content-Type": "application/json" },
			body: JSON.stringify({ websiteUrl: website_url, task }),
		});

		if (!startRes.ok) {
			const err = await startRes.text();
			return { content: [{ type: "text", text: `Error: ${startRes.status} ${err}` }] };
		}

		const { id } = (await startRes.json()) as { id: string };
		const pollUrl = `${base}/automate/${id}`;

		for (let i = 0; i < 60; i++) {
			await new Promise((resolve) => setTimeout(resolve, 3000));
			const pollRes = await fetch(pollUrl);
			if (!pollRes.ok) {
				continue;
			}

			const { status, output } = (await pollRes.json()) as {
				status: string;
				output?: { githubUrl?: string };
			};

			if (status === "complete" && output?.githubUrl) {
				return { content: [{ type: "text", text: `Done. GitHub repo: ${output.githubUrl}` }] };
			}
			if (status === "errored") {
				return { content: [{ type: "text", text: `Workflow failed. Check ${pollUrl}` }] };
			}
		}

		return { content: [{ type: "text", text: `Workflow started. Poll status: ${pollUrl}` }] };
	},
);

const mcpHandler = createMcpHandler(mcpServer);

/**
 * POST /automate - Trigger Pokeforge workflow (Stagehand + MCP codegen + GitHub)
 * Body: { websiteUrl: string, task: string }
 */
app.post("/automate", async (c) => {
	const body = (await c.req.json()) as { websiteUrl?: string; task?: string };
	const { websiteUrl, task } = body;
	if (!websiteUrl || !task) {
		return c.json({ error: "websiteUrl and task are required" }, 400);
	}
	const instance = await c.env.POKEFORGE_WORKFLOW.create({
		params: { websiteUrl, task },
	});
	const status = await instance.status();
	return c.json({ id: instance.id, details: status });
});

/**
 * GET /automate/:id - Fetch status and result of Pokeforge workflow
 */
app.get("/automate/:id", async (c) => {
	const instanceId = c.req.param("id");
	if (!instanceId) {
		return c.json({ error: "Instance ID not provided" }, 400);
	}
	const instance = await c.env.POKEFORGE_WORKFLOW.get(instanceId);
	const status = await instance.status();
	const output = "output" in instance && typeof (instance as { output?: () => Promise<unknown> }).output === "function" ? await (instance as { output: () => Promise<unknown> }).output() : undefined;
	return c.json({ status, output });
});

/**
 * POST /debug/stagehand-smoke - Verify orchestrator -> stagehand transport on Cloudflare.
 * Body (optional): { websiteUrl?: string, task?: string }
 */
app.post("/debug/stagehand-smoke", async (c) => {
	const body = (await c.req.json().catch(() => ({}))) as {
		websiteUrl?: string;
		task?: string;
	};
	const websiteUrl = body.websiteUrl ?? "https://example.com";
	const task = body.task ?? "Observe the page and describe the main content.";

	try {
		const discovery = await runStagehandDiscovery({
			stagehandService: c.env.STAGEHAND_SERVICE,
			stagehandUrl: c.env.STAGEHAND_SERVICE_URL,
			websiteUrl,
			task,
			context: { smokeTest: true },
		});

		return c.json({
			ok: !discovery.error,
			transport: c.env.STAGEHAND_SERVICE ? "service-binding" : "url-fallback",
			cacheKey: discovery.cacheKey,
			sessionId: discovery.sessionId,
			receivedArtifactCount: discovery.receivedArtifactCount ?? 0,
			actionCount: discovery.actions.length,
			artifactCount: discovery.artifacts.length,
			error: discovery.error,
			logPreview: discovery.logs.slice(0, 400),
		});
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		return c.json(
			{
				ok: false,
				transport: c.env.STAGEHAND_SERVICE ? "service-binding" : "url-fallback",
				error: message,
			},
			500,
		);
	}
});

export default {
	fetch(request: Request, env: Env, ctx: ExecutionContext) {
		const pathname = new URL(request.url).pathname;
		if (pathname.startsWith("/mcp")) {
			return mcpHandler(request, env, ctx);
		}
		return app.fetch(request, env, ctx);
	},
} satisfies ExportedHandler<Env>;

```

### orchestrator-workers/vitest.config.ts

```typescript
import { defineConfig } from "vitest/config";

export default defineConfig({
	test: {
		passWithNoTests: true,
	},
});

```

### discovery/replay.sh

```shell
#!/bin/bash
ulimit -n 65536 2>/dev/null || ulimit -n 10240 2>/dev/null || ulimit -n 4096 2>/dev/null || true
exec npx tsx src/replay-endpoint.ts "$@"

```

### orchestrator-workers/vite.config.ts

```typescript
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
	plugins: [cloudflare()],
});

```

### discovery/discover.sh

```shell
#!/bin/bash
# Raise open-files limit as high as possible so tsx + Stagehand don't hit EMFILE
ulimit -n 65536 2>/dev/null || ulimit -n 10240 2>/dev/null || ulimit -n 4096 2>/dev/null || true
echo "Open files limit: $(ulimit -n)"
exec npx tsx src/run-discovery.ts "$@"

```

### stagehand-service/vite.config.ts

```typescript
import { cloudflare } from "@cloudflare/vite-plugin";
import { fileURLToPath } from "node:url";
import { resolve } from "node:path";
import { defineConfig } from "vite";

const wsShimPath = fileURLToPath(new URL("./src/shims/ws.ts", import.meta.url));

export default defineConfig({
	plugins: [cloudflare()],
	resolve: {
		// Prevent packages like `ws` from resolving to browser-only shims.
		conditions: ["workerd", "worker", "node", "module"],
		alias: {
			ws: resolve(wsShimPath),
		},
	},
});

```

[21 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]