# How AI Agents Are Authorized in OpenSEO: MCP Token Validation and Project Scoping

> Learn how AI agents are authorized in OpenSEO. Discover MCP token validation and project scoping with the withMcpProjectAuth wrapper for secure access.

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

---

**AI agents in OpenSEO are authorized per-project through the `withMcpProjectAuth` wrapper, which validates that the caller's organization token owns the requested project before executing MCP tool handlers.**

OpenSEO uses the Model Context Protocol (MCP) to expose SEO tools—such as SERP scraping and backlink analysis—to AI agents. Understanding how AI agents are authorized in OpenSEO requires examining the token-based validation pipeline that runs through the MCP transport layer and project-specific access controls implemented in the `every-app/open-seo` repository.

## The MCP Authorization Architecture

AI agents interact with OpenSEO by calling MCP tools exposed via server endpoints. Authorization is enforced at two critical layers: the transport layer validates the authentication token, and the project-auth layer verifies organizational ownership of the target resource.

### Token Validation at the Transport Layer

According to the OpenSEO source code, incoming MCP requests first pass through the transport layer defined in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts). This layer inspects the request's `authMode` and extracts the authentication context—containing fields such as `userId` and `organizationId`—from the bearer token. The validated `auth` object is then propagated to downstream tool handlers.

### Project-Scope Verification with `withMcpProjectAuth`

Once the token is validated, the `withMcpProjectAuth` function—located in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts)—acts as a gatekeeper. This higher-order function wraps individual MCP tool handlers and performs the following checks:

1. Extracts the `projectId` from the tool arguments.
2. Calls `ProjectService.getProjectForOrganization` to verify the project belongs to the organization encoded in the auth token.
3. Throws a `FORBIDDEN` error if the association fails.
4. Returns an enriched context containing `auth`, `baseUrl`, billing helpers, and the full project record if validation succeeds.

## Authorization Wrapper Implementation

The `withMcpProjectAuth` utility ensures that AI agents cannot access projects outside their authorized organization. Below is the core implementation pattern from [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts):

```typescript
// src/server/mcp/project-auth.ts
export function withMcpProjectAuth<TArgs extends { projectId: string }, TResult>(
  handler: (args: TArgs, ctx: McpProjectAuthContext) => Promise<TResult> | TResult,
) {
  return async (args: TArgs, toolContext: ToolContext) => {
    // 1️⃣ Verify the caller's organization owns the project
    const context = await requireProjectAccess(toolContext, args.projectId);
    // 2️⃣ Run the actual tool with the verified context
    return handler(args, context);
  };
}

```

To expose a tool to AI agents, you wrap the business logic handler with this utility:

```typescript
// Example: exposing a keyword-research tool to AI agents
import { withMcpProjectAuth } from "@/server/mcp/project-auth";

async function getKeywordSuggestions(
  args: { projectId: string; query: string },
  ctx: McpProjectAuthContext
) {
  // ctx.auth contains verified userId and organizationId
  // ctx.project contains the full project record
  return await fetchKeywordData(args.query, ctx.project.id);
}

export const getKeywordSuggestionsTool = withMcpProjectAuth(getKeywordSuggestions);

```

## Protecting Server Functions with Project Context

Beyond MCP tools, OpenSEO also protects server-side functions using middleware that enforces the same authorization rules. The `requireProjectContext` middleware—defined in [`src/serverFunctions/middleware.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/middleware.ts)—extracts the authenticated user context and guarantees that a valid project is present before the handler executes.

For example, the AI-visibility endpoint in [`src/serverFunctions/ai-search.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ai-search.ts) uses this pattern to ensure agents can only access paid features for authorized projects:

```typescript
// src/serverFunctions/ai-search.ts
export const lookupBrand = createServerFn({ method: "POST" })
  .middleware(requireProjectContext)           // ✅ Ensures authenticated project context
  .validator(brandLookupInputSchema)
  .handler(async ({ data, context }) => {
    await assertPaidPlan(context.organizationId); // Paid-plan gate
    return getBrandLookup({ ...data, projectId: context.projectId }, context);
  });

```

## Key Files in the Authorization Pipeline

The following files collectively implement the authorization chain that secures AI agent access in OpenSEO:

| File | Role |
|------|------|
| [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) | Core wrapper that validates a project ID against the caller's organization token. |
| [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) | Validates incoming MCP request auth modes and populates the `auth` object. |
| `src/server/mcp/tools/*` | Directory containing AI-agent tools (keyword research, SERP fetch) that use `withMcpProjectAuth`. |
| [`src/serverFunctions/middleware.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/middleware.ts) | Middleware extracting authenticated user context and enforcing project presence. |
| [`src/serverFunctions/ai-search.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ai-search.ts) | Example server function applying project-context middleware and paid-plan checks. |

## Summary

- **Transport-layer validation** in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) authenticates the organization token before any tool logic executes.
- **Project-scope gates** via `withMcpProjectAuth` in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) ensure AI agents can only access projects belonging to their authenticated organization.
- **Context enrichment** provides handlers with verified `auth` data, `project` records, and billing helpers after successful authorization.
- **Server-function middleware** (`requireProjectContext`) extends the same security model to non-MCP server endpoints.
- **Paid-plan enforcement** occurs after authorization checks, ensuring proper feature gating.

## Frequently Asked Questions

### What is MCP in OpenSEO?

MCP stands for Model Context Protocol, the communication standard used by OpenSEO to expose SEO tools—such as backlink analysis and keyword research—to AI agents. MCP defines how agents request data and how the server validates those requests before returning results.

### How does OpenSEO prevent AI agents from accessing other organizations' projects?

OpenSEO prevents cross-organization access through the `withMcpProjectAuth` wrapper in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts). This function queries the database to verify that the requested `projectId` belongs to the `organizationId` encoded in the caller's authentication token. If the project is not found or belongs to a different organization, the wrapper immediately throws a `FORBIDDEN` error before the tool handler executes.

### Can AI agents access paid features without proper authorization?

No. After passing the initial project authorization checks, handlers like `lookupBrand` in [`src/serverFunctions/ai-search.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/ai-search.ts) explicitly call `assertPaidPlan(context.organizationId)`. This secondary check ensures the organization has an active paid subscription before allowing access to premium AI-visibility features, preventing unauthorized usage even if the agent has valid project access.

### Where is the auth token validated in the MCP pipeline?

The auth token is first validated in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts), where the transport layer inspects the request's `authMode` and extracts the authentication context. This validated context—containing the user and organization identifiers—is then passed to tool wrappers like `withMcpProjectAuth`, which perform additional project-specific authorization before executing business logic.