# How OpenSEO MCP Server Authorization and Project Authentication Work

> Understand OpenSEO MCP server authorization and project authentication. Discover how OpenSEO secures access with type-safe context objects and explicit organization-project validation gates.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-07-27

---

**OpenSEO implements a two-stage security architecture that first authenticates users via a type-safe context object stored in AsyncLocalStorage, then authorizes project access through explicit organization-project validation gates.**

OpenSEO (every-app/open-seo) secures its Model-Context-Protocol (MCP) layer through a strict separation between authentication and project authorization. This design ensures that every request reaching a server-side function carries validated user credentials while enforcing rigid access controls that verify organization ownership before allowing project-specific operations.

## Authentication Flow: Establishing the McpToolAuthContext

The authentication stage materializes caller credentials into a validated **McpToolAuthContext** object. According to the OpenSEO source code, this context contains the user’s ID, email, organization ID, scopes, and the base URL of the MCP endpoint.

In [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts) (lines **[25‑34]**), a Zod schema defines the strict structure of this auth payload. The system stores the validated context in an **AsyncLocalStorage** instance (lines **[46‑55]**), creating an asynchronous context store that eliminates the need to pass authentication data through every function signature manually.

The helper function `requireMcpToolAuthContext` (lines **[92‑107]**) retrieves this context from the async store or extracts it from `extra.authInfo` for callers that supply it directly. This pattern guarantees that downstream tools always access type-safe, validated authentication data without repeating validation logic.

## Project Authorization: Enforcing Organization-Project Boundaries

After authentication, requests targeting specific projects must prove the authenticated user belongs to the organization that owns the target project. This authorization layer prevents cross-organization data access through explicit database verification.

In [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) (lines **[13‑25]**), the `requireProjectAccess` function loads the project using `ProjectService.getProjectForOrganization(orgId, projectId)`. If the lookup fails—indicating the project does not belong to the user’s organization—the function throws a `FORBIDDEN` error. This creates a hard security gate that persists even if underlying service error handling changes.

Upon successful validation, the function assembles a **project-scoped context** (lines **[27‑34]**) containing the original auth information, a billing helper, and the full project row. The `withMcpProjectAuth` wrapper (lines **[39‑49]**) encapsulates this logic, allowing any tool handler to enforce project authorization declaratively.

## Middleware Integration with TanStack Server Functions

OpenSEO integrates these security layers into its TanStack Server Function pipeline through middleware composition. The [`src/serverFunctions/middleware.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/middleware.ts) file orchestrates the authentication and authorization checks for every server function.

Lines **[16‑24]** implement `requireAuthenticatedContext`, which validates the base context and ensures every request carries a valid `EnsuredUserContext`. For operations requiring project access, lines **[42‑60]** add the project guard through `requireProjectContext`, injecting the validated `projectId` and `project` objects into the function context.

This middleware approach ensures that business logic remains free of security boilerplate while maintaining guarantee that authentication and authorization checks execute before any data operations.

## Practical Implementation Examples

### Retrieving Authentication Context Directly

Tools that only need user identity can access the authentication context directly using the `requireMcpToolAuthContext` helper:

```typescript
import { requireMcpToolAuthContext } from "@/server/mcp/context";

export async function whoAmI(extra: ToolExtra) {
  const auth = requireMcpToolAuthContext(extra);
  return { userId: auth.userId, email: auth.userEmail, org: auth.organizationId };
}

```

*Source:* [`src/server/mcp/tools/whoami.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/whoami.ts) (lines **[1‑9]**)

### Enforcing Project Access with the Authorization Wrapper

Project-scoped tools use the `withMcpProjectAuth` wrapper to automatically enforce organization-project matching:

```typescript
import { withMcpProjectAuth } from "@/server/mcp/project-auth";

type Args = { projectId: string; keyword: string };

export const getKeywordSuggestions = withMcpProjectAuth<Args, Suggestion[]>(
  async ({ projectId, keyword }, ctx) => {
    // `ctx.project` is guaranteed to belong to `ctx.auth.organizationId`
    // `ctx.billing` can be passed to downstream billing APIs
    return await KeywordService.suggest(keyword, ctx.project.id);
  },
);

```

*Source:* [`src/server/mcp/tools/search-console-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/search-console-tools.ts) (lines **[45‑53]**)

### Middleware Pipeline Integration

Server functions integrate these checks through middleware composition:

```typescript
export const requireAuthenticatedContext = [
  createMiddleware({ type: "function" }).server(async ({ next, context }) => {
    const auth = getAuthenticatedContext(context); // validates via Zod
    return next({ context: auth });
  }),
];

```

*Source:* [`src/serverFunctions/middleware.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/middleware.ts) (lines **[32‑40]**)

## Summary

- **Authentication** is handled via `McpToolAuthContext` in [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts), validated with Zod and stored in AsyncLocalStorage for downstream access without parameter drilling.
- **Authorization** enforces organization-project boundaries through `requireProjectAccess` in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts), which performs explicit database lookups and returns forbidden errors for unauthorized access attempts.
- **Integration** occurs through TanStack Server Function middleware in [`src/serverFunctions/middleware.ts`](https://github.com/every-app/open-seo/blob/main/src/serverFunctions/middleware.ts), ensuring every endpoint receives validated contexts before executing business logic.
- **Developer experience** is streamlined through helpers like `withMcpProjectAuth`, which wrap tool handlers to automatically inject authorized contexts and billing helpers.

## Frequently Asked Questions

### What data does McpToolAuthContext contain?

**McpToolAuthContext** contains the user’s ID, email address, organization ID, authorized scopes, and the MCP base URL. This schema is strictly defined using Zod in [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts) (lines **[25‑34]**) to ensure type safety across the entire request lifecycle.

### How does OpenSEO prevent users from accessing projects outside their organization?

The system enforces a hard gate through `requireProjectAccess` in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts) (lines **[13‑25]**). This function queries the database to verify that the requested project belongs to the organization encoded in the authentication token. If the lookup fails, it immediately throws a `FORBIDDEN` error before any project data returns to the caller.

### Why does OpenSEO use AsyncLocalStorage for authentication context?

**AsyncLocalStorage** eliminates the need to pass authentication parameters through every function signature in the call stack. By storing the validated `McpToolAuthContext` in async local storage (implemented in [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts), lines **[46‑55]**), downstream tools can access user credentials implicitly while maintaining isolation between concurrent requests.

### Can individual MCP tools override or skip project authorization?

No. Project authorization is enforced through the `withMcpProjectAuth` wrapper (lines **[39‑49]** in [`src/server/mcp/project-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/project-auth.ts)), which executes the database verification before invoking the tool handler. This design ensures that authorization logic cannot be bypassed accidentally, as the wrapper controls the execution context and only proceeds if the organization-project match validates successfully.