# How to Contribute to the OpenSEO Project: A Developer's Guide to Full-Stack TypeScript and Cloudflare Workers

> Contribute to OpenSEO by setting up Node 20+, using /simple-issue-description, and submitting tested PRs for server functions, database schemas, or MCP methods.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-15

---

**To contribute to the OpenSEO project, set up a local development environment with Node 20+ and Corepack, create reproducible issues using the `/simple-issue-description` skill, and submit tested code changes via PRs that align with the established patterns for server functions, database schemas, or MCP methods.**

OpenSEO is a modern, full-stack TypeScript application that runs on **Cloudflare Workers** (or Docker for self-hosting) to provide SEO tools through API endpoints, **Machine-Client-Protocol (MCP)** integrations, and persistent AI agents. Whether you are adding new routes to `every-app/open-seo`, extending the **Drizzle ORM** schemas, or exposing functionality to AI agents, understanding the repository's architecture ensures your contributions are efficient and review-ready.

## Understanding the Core Architecture

The application entry point at [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) handles all request routing, authentication, and protocol wiring for the Cloudflare Worker environment.

### Request Routing and MCP Implementation

The [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) file creates the TanStack Start handler and initializes the OAuth provider for agent connections:

- **[`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) (lines 15-21, 73-75)**: Exposes `createOpenSeoOAuthProvider` and `handleSelfHostedOpenSeoMcpRequest` to provide JSON-RPC-style APIs for AI agents (Claude Code, OpenClaw, Hermes).
- **[`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) (lines 33-99)**: Exports Durable Object classes for stateful backends (onboarding chat and SAM chat agents), where each connection is authorized before the WebSocket upgrade occurs.
- **`src/serverFunctions/*`**: Houses individual API endpoints (e.g., [`src/serverFunctions/projects.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/projects.ts)) automatically routed to `/api/*`.

### Database Layer with Drizzle ORM

OpenSEO uses **Drizzle ORM** with adapters for both **D1 (SQLite)** and **PostgreSQL**. Schema definitions reside in `src/db/*.schema.ts`, while [`src/db/provider.ts`](https://github.com/every-app/open-seo/blob/main/src/db/provider.ts) exposes `withPgClient` to supply a per-request transaction-aware client. When contributing database features, you must create migration scripts in the `scripts/` directory and ensure changes are compatible with both D1 and PostgreSQL backends.

### Authentication and Middleware

Authentication is controlled via the `AUTH_MODE` environment variable, defined in [`src/lib/auth-mode.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-mode.ts):

- **`cloudflare_access`**: Validates Cloudflare Access JWTs.
- **`local_noauth`**: Fast development mode that bypasses authentication.
- **`hosted`**: Better Auth implementation for email/password credentials.

The `src/middleware/ensure-user/` directory contains middleware that resolves the current user from request headers, used by most server functions to enforce authorization.

### Durable Object Workflows

Long-running background tasks like site audits and rank checks use **Durable Objects** defined in `src/server/workflows/*`. These maintain state across requests and handle background processing independently of the main request flow.

## Contribution Workflow and Guidelines

OpenSEO follows an issue-first workflow documented in [`docs/CONTRIBUTING.md`](https://github.com/every-app/open-seo/blob/main/docs/CONTRIBUTING.md). The project prefers well-written issues over direct PRs to ensure alignment before code is written.

### 1. Create a Reproducible Issue

Install the project's skill to generate structured issue descriptions:

```bash
npx skills add every-app/open-seo --skill simple-issue-description

```

Use the `/simple-issue-description` command to create detailed, reproducible issues that describe bugs or feature requests according to the project's standards.

### 2. Local Development Setup

Configure your environment with Node 20+, Corepack, and a DataForSEO API key following [`docs/LOCAL_DEVELOPMENT.md`](https://github.com/every-app/open-seo/blob/main/docs/LOCAL_DEVELOPMENT.md):

```bash
corepack enable
pnpm install --frozen-lockfile
pnpm run db:migrate:local
pnpm dev:agents

```

The `pnpm dev:agents` command starts the worker at `http://open-seo.localhost:1355` with hot reloading and agent support enabled.

### 3. Testing Requirements

All contributions require accompanying tests:

- **Unit tests**: Co-located as `*.test.ts` files alongside implementations.
- **End-to-end tests**: Playwright specs for critical UI workflows.

Run the full suite before submitting:

```bash
pnpm test
pnpm e2e

```

## Common Contribution Paths

### Adding API Endpoints

Create a new file under `src/serverFunctions/` and export an async handler. The TanStack Start router automatically exposes these at `/api/*`:

```typescript
// src/serverFunctions/insights.ts
import { json } from "@remix-run/cloudflare";

export async function GET(request: Request) {
  const url = new URL(request.url);
  const keyword = url.searchParams.get("keyword");
  if (!keyword) return json({ error: "Missing keyword" }, { status: 400 });

  const data = await getKeywordOpportunities(keyword);
  return json({ keyword, opportunities: data });
}

```

### Extending the Database Schema

Add new tables in `src/db/[feature].schema.ts` using Drizzle's PostgreSQL core:

```typescript
// src/db/insights.schema.ts
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";

export const keywordOpportunities = pgTable("keyword_opportunities", {
  id: serial("id").primaryKey(),
  keyword: text("keyword").notNull(),
  opportunityScore: text("opportunity_score").notNull(),
  createdAt: timestamp("created_at").defaultNow(),
});

```

Create a migration script in `scripts/` and run `pnpm run db:migrate:local` to apply changes to your local SQLite (D1) database.

### Exposing MCP Methods for AI Agents

To make functionality available to AI agents, define methods in the MCP layer and register them in [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts):

```typescript
// src/server/mcp/insights.ts
import { defineMcpMethod } from "agents/mcp";

export const getKeywordOpportunities = defineMcpMethod(
  "getKeywordOpportunities",
  async (keyword: string) => {
    return await getKeywordOpportunitiesFromDb(keyword);
  }
);

```

Add the export to the MCP method list at the bottom of [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) where other Durable Object classes are exported.

### Writing Unit Tests

Co-locate tests with implementation files using the [`.test.ts`](https://github.com/every-app/open-seo/blob/main/.test.ts) extension:

```typescript
// src/serverFunctions/insights.test.ts
import { GET } from "./insights";

test("GET returns 400 when keyword missing", async () => {
  const response = await GET(new Request("http://localhost/api/insights"));
  expect(response.status).toBe(400);
});

```

## Summary

- **Architecture**: OpenSEO runs on Cloudflare Workers with entry point [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts), using TanStack Start for routing and Durable Objects for stateful agents.
- **Database**: Contributions require schema updates in `src/db/*.schema.ts`, migration scripts, and repository classes that use `withPgClient` from [`src/db/provider.ts`](https://github.com/every-app/open-seo/blob/main/src/db/provider.ts).
- **Authentication**: Set `AUTH_MODE` to `local_noauth` for development, or use `cloudflare_access`/`hosted` for production-like auth testing.
- **Workflow**: Create issues first using `/simple-issue-description`, develop locally with `pnpm dev:agents`, and verify with `pnpm test` and `pnpm e2e`.
- **Integration**: Expose features via server functions (`src/serverFunctions/`) for HTTP APIs and MCP methods (`src/server/mcp/`) for AI agent access.

## Frequently Asked Questions

### Do I need a Cloudflare account to contribute to OpenSEO?

No. While production runs on Cloudflare Workers, you can develop locally using the `local_noauth` mode and a local SQLite (D1) database. The `pnpm dev:agents` command simulates the Cloudflare Worker environment at `http://open-seo.localhost:1355` without requiring a Cloudflare account, though you will need a DataForSEO API key for full functionality.

### What is the difference between server functions and MCP methods?

**Server functions** in `src/serverFunctions/` expose HTTP endpoints at `/api/*` for web clients and external services. **MCP methods** in `src/server/mcp/` expose JSON-RPC procedures that AI agents consume through the Machine-Client-Protocol via `handleSelfHostedOpenSeoMcpRequest` (as implemented in [`src/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server.ts) lines 73-75). If your feature must be accessible to both humans and AI agents, implement the core logic in a repository class, then expose it via both a server function and an MCP method.

### How do I handle database migrations when contributing new schemas?

After modifying or adding schema files in `src/db/*.schema.ts`, create a migration script in the `scripts/` directory. Run `pnpm run db:migrate:local` to apply changes to your local D1 (SQLite) database. Ensure your repository class uses `withPgClient` from [`src/db/provider.ts`](https://github.com/every-app/open-seo/blob/main/src/db/provider.ts) to maintain compatibility with both D1 and PostgreSQL backends, using transactions where appropriate.

### What authentication mode should I use for local development?

Use `AUTH_MODE=local_noauth` for the fastest setup, which bypasses authentication checks entirely. For testing Cloudflare Access integration locally, use `cloudflare_access` mode with valid JWTs. Use `hosted` mode only when specifically testing the Better Auth email/password flow or user management features that require Better Auth.