How to Contribute to the OpenSEO Project: A Developer's Guide to Full-Stack TypeScript and Cloudflare Workers
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 handles all request routing, authentication, and protocol wiring for the Cloudflare Worker environment.
Request Routing and MCP Implementation
The src/server.ts file creates the TanStack Start handler and initializes the OAuth provider for agent connections:
src/server.ts(lines 15-21, 73-75): ExposescreateOpenSeoOAuthProviderandhandleSelfHostedOpenSeoMcpRequestto provide JSON-RPC-style APIs for AI agents (Claude Code, OpenClaw, Hermes).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) 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 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:
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. 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:
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:
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.tsfiles alongside implementations. - End-to-end tests: Playwright specs for critical UI workflows.
Run the full suite before submitting:
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/*:
// 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:
// 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:
// 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 where other Durable Object classes are exported.
Writing Unit Tests
Co-locate tests with implementation files using the .test.ts extension:
// 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, 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 usewithPgClientfromsrc/db/provider.ts. - Authentication: Set
AUTH_MODEtolocal_noauthfor development, or usecloudflare_access/hostedfor production-like auth testing. - Workflow: Create issues first using
/simple-issue-description, develop locally withpnpm dev:agents, and verify withpnpm testandpnpm 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →