MCP Server Implementation in Instatic: Architecture and External Agent Integration
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.
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 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.
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, which instantiates the MCP server using the official SDK. The server creates a tool registry defined in 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 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. 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 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.
// 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. 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.
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 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/sdkpackage 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. - Capability Filtering: The registry in
server/ai/mcp/registry.tsfilters available tools based on granted permissions before execution. - Execution Contexts: Headless tools run directly against the database, while browser-relay tools use
editorBridge.tsto 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. This endpoint accepts POST requests with JSON-RPC formatted tool invocations, authenticates the bearer token via 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 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 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.
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 →