# MCP Server Implementation in Instatic: Architecture and External Agent Integration

> Discover the Instatic MCP server implementation. Learn how this component exposes capability-filtered tools to external AI agents via HTTP with secure authentication and live workspace operations.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: architecture
- Published: 2026-07-28

---

**Instatic implements a Model Context Protocol (MCP) server in `server/ai/mcp/` that exposes capability-filtered tools to external AI agents via HTTP, using hashed bearer tokens (OAuth or personal access tokens) for authentication and an editor bridge for live workspace operations.**

Instatic serves as a fully functional MCP (Model Context Protocol) server, enabling external AI agents like Claude or Codex to interact with its content management capabilities. This implementation follows the official MCP wire protocol using the `@modelcontextprotocol/sdk` package, isolated within the `server/ai/mcp/` module to maintain clean architectural boundaries.

## Authentication Flow and Bearer Token Resolution

External agents initiate connections by sending HTTP requests to `https://<host>/_instatic/mcp` with an **Authorization** header. Instatic supports two distinct authentication modes, both resolving to the same underlying connector infrastructure in [`server/ai/mcp/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/auth.ts).

**Personal Access Tokens (PATs)** follow the format `Bearer imcp_pat_…` and are generated via the MCP UI. **OAuth Access Tokens** follow `Bearer imcp_at_…` and are obtained through the hosted OAuth flow. The `resolveMcpAuth()` function in [`server/ai/mcp/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/auth.ts) parses the bearer, distinguishes between these token types, and performs the corresponding database lookup via `findOAuthAccessGrant` or `findConnectionByTokenHash`.

The function returns a `McpAuthResult` containing the **connector ID**, **user ID**, and **granted capabilities**. If authentication fails, the system returns an RFC 9728-aware 401 response with a `WWW-Authenticate` header pointing to the protected-resource metadata URL.

```typescript
import { resolveMcpAuth } from '@core/ai/mcp/auth';
import { db } from '@/server/db/client';

export async function handleMcpRequest(req: Request) {
  const auth = await resolveMcpAuth(req, db);
  if (!auth.ok) return unauthorizedResponse(req);
  
  // auth contains connectorId, userId, and capabilities
  // Proceed to tool dispatch...
}

```

## MCP Server Architecture and Tool Registry

After authentication, requests reach [`server/ai/mcp/server.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/server.ts), which instantiates the MCP server using the official SDK. The server creates a **tool registry** defined in [`server/ai/mcp/registry.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/registry.ts) that manages the complete catalog of Instatic capabilities.

The registry loads all available tools—including `site_list_documents`, `site_read_styles`, and `site_publish`—then filters the catalog based on the connector's specific capabilities using `toolAllowedForCapabilities()`. This ensures agents can only invoke tools explicitly granted during the connection setup, such as `ai.tools.write` or `pages.publish`.

The transport layer in [`server/ai/mcp/transports/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/transports/http.ts) implements the stateless HTTP transport required by the MCP SDK, handling the wire protocol serialization while the server manages tool dispatch and capability enforcement.

## Tool Execution: Headless and Browser-Relay Modes

Instatic distinguishes between two execution contexts for MCP tools, implemented across the `server/ai/mcp/tools/` directory.

**Headless tools** operate directly against the database and publisher layers without UI interaction. These include read-only operations like `site_list_documents` and `site_read_styles`, which execute immediately via `executeAiTool()` and return static data.

**Browser-relay tools** require interaction with the live editor workspace. These operations—such as page-tree mutations and live-DOM inspection—route through [`server/ai/mcp/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/editorBridge.ts). The bridge locates the **open workspace** belonging to the connector's owner and forwards the call to the live editor store, ensuring that drafts remain the single source of truth and preventing conflicts with active user sessions.

The `site_publish` operation in [`server/ai/mcp/tools/publishTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/tools/publishTool.ts) demonstrates a privileged headless tool. It requires both `ai.tools.write` and `pages.publish` capabilities, executes the full static-site pipeline, atomically swaps the static artifact, and records an audit event including the originating connector ID.

```typescript
// Calling the MCP endpoint from an external client
curl -X POST https://my-instatic.com/_instatic/mcp \
  -H "Authorization: Bearer imcp_pat_0123abcd..." \
  -H "Content-Type: application/json" \
  -d '{
        "tool": "site_publish",
        "args": {}
      }'

```

## Connection Lifecycle: OAuth vs Personal Access Tokens

The `server/ai/mcp/connectors/` module manages persistent connector state in the `ai_mcp_connectors` table, which stores the capability set (`capabilities_json`) and lifecycle timestamps (`created_at`, `last_used_at`, `revoked_at`, `expires_at`).

**OAuth mode** hosts dynamic client registration at `/_instatic/oauth/register` and token exchange at `/_instatic/oauth/token`, implemented in [`server/ai/mcp/oauth/handler.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/oauth/handler.ts). Tokens are stored as **SHA-256 hashed values**, rotate on each refresh, expire after one hour maximum, and the grant itself expires after 90 days.

**Personal-access-token mode** generates tokens on-demand via the MCP UI, stores only the SHA-256 hash, and allows independent revocation. When creating a token programmatically via `createPersonalToken()` in the connectors store, the plaintext token returns exactly once—only its hash persists.

```typescript
import { createPersonalToken } from '@core/ai/mcp/connectors/store';

// After step-up authentication:
const { token } = await createPersonalToken(db, {
  userId: currentUser.id,
  label: 'my-cli-client',
  capabilities: ['ai.tools.write', 'pages.publish'],
  expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
});
// Return plaintext token once; only hash is stored

```

## Security Model and Audit Logging

Instatic's MCP implementation enforces security at multiple layers. All tokens are **hashed using SHA-256** before storage; plain tokens never persist in the database. Capability checks occur both at the tool catalog filtering stage and within individual tool implementations.

The system generates comprehensive audit events for the `ai.mcp_connector` namespace, including `created`, `revoked`, and `used` events. These records capture the connector ID and authentication mode, enabling complete traceability without exposing secret values. The [`paths.ts`](https://github.com/CoreBunch/Instatic/blob/main/paths.ts) file defines canonical endpoints for MCP, OAuth metadata, and consent URLs, ensuring consistent routing across the application.

## Summary

- **Isolated Architecture**: The MCP server implementation is confined to `server/ai/mcp/`, using the `@modelcontextprotocol/sdk` package exclusively within this boundary.
- **Dual Authentication**: Supports both OAuth 2.0 flows (90-day grants, 1-hour tokens) and Personal Access Tokens via [`server/ai/mcp/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/auth.ts).
- **Capability Filtering**: The registry in [`server/ai/mcp/registry.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/registry.ts) filters available tools based on granted permissions before execution.
- **Execution Contexts**: Headless tools run directly against the database, while browser-relay tools use [`editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/editorBridge.ts) to interact with live workspaces.
- **Security-First**: All tokens are SHA-256 hashed, capabilities are enforced at multiple layers, and audit events track connector usage without leaking secrets.

## Frequently Asked Questions

### What is the entry point for MCP requests in Instatic?

The entry point is the `/_instatic/mcp` HTTP endpoint, handled by [`server/ai/mcp/server.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/server.ts). This endpoint accepts POST requests with JSON-RPC formatted tool invocations, authenticates the bearer token via [`server/ai/mcp/auth.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/auth.ts), and dispatches to the appropriate tool handler through the registry.

### How does Instatic handle live editing via MCP without corrupting user sessions?

Instatic uses [`server/ai/mcp/editorBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/mcp/editorBridge.ts) to route browser-relay tools to the live editor workspace. The bridge identifies the correct workspace for the authenticated user and forwards calls to the editor store, ensuring that MCP operations respect the draft state and active user interactions rather than writing directly to the database.

### What capabilities are required to publish content through the MCP server?

Publishing requires both `ai.tools.write` and `pages.publish` capabilities. These must be granted during the OAuth consent flow or included in the Personal Access Token scope. The [`publishTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/publishTool.ts) implementation checks these permissions before executing the static-site generation pipeline.

### How long do MCP authentication tokens remain valid?

OAuth access tokens expire after one hour and rotate on each refresh, while the underlying grant expires after 90 days. Personal Access Tokens can be configured with custom expiration dates (up to 30 days by default) and remain valid until revoked or expired, whichever comes first.