# How Instatic's AI Agent Integrates with Multiple Providers (Claude, OpenAI, Ollama)

> Discover how Instatic's AI agent seamlessly integrates with Claude, OpenAI, and Ollama. Learn about the ProviderAdapter interface and unified response format for efficient tool execution.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: how-to-guide
- Published: 2026-07-03

---

**Instatic normalizes Claude, OpenAI, and Ollama behind a unified ProviderAdapter interface that translates all responses into an OpenAI-Responses wire format, enabling seamless tool execution and MCP bridging across any LLM backend.**

Instatic is an open-source visual editor that ships with a provider-agnostic AI agent capable of routing requests to Anthropic's Claude, OpenAI's GPT models, and local Ollama instances. The architecture abstracts provider-specific implementations behind a common interface defined in [`server/ai/drivers/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/types.ts), ensuring that tool loops and external MCP clients work identically regardless of which LLM powers the backend.

## Provider Adapter Architecture

The foundation of Instatic's multi-provider support rests in the **`ProviderAdapter`** interface defined in [`server/ai/drivers/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/types.ts). This contract standardizes how the application communicates with disparate LLM APIs.

Each supported provider implements this interface through dedicated driver files:

- **[`anthropic.ts`](https://github.com/CoreBunch/Instatic/blob/main/anthropic.ts)** – Maps Claude's proprietary API to the internal OpenAI-Responses format
- **[`openai.ts`](https://github.com/CoreBunch/Instatic/blob/main/openai.ts)** – Implements the official OpenAI Responses API
- **[`ollama.ts`](https://github.com/CoreBunch/Instatic/blob/main/ollama.ts)** – Wraps local OpenAI-compatible endpoints

All drivers translate their native response schemas into Instatic's **OpenAI-Responses** wire format, ensuring downstream components receive consistent `MessageStreamEvent` objects regardless of the upstream provider.

## Shared HTTP Abstraction Layer

Rather than importing heavy SDKs, Instatic uses a thin HTTP helper layer located in [`server/ai/drivers/http/chatCompletions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/chatCompletions.ts). This module handles streaming Server-Sent Events (SSE), error classification, and request construction for the standard chat completions endpoint.

The **`makeChatCompletionsAdapter`** function creates a reusable adapter that any OpenAI-compatible service can leverage. For example, the Ollama driver simply passes its local configuration to this factory:

```typescript
// server/ai/drivers/ollama.ts
import { makeChatCompletionsAdapter } from './http/chatCompletions';
import type { ProviderConfig } from './types';

export function createAdapter(cfg: ProviderConfig) {
  return makeChatCompletionsAdapter({
    baseUrl: cfg.baseUrl,          // e.g., http://localhost:11434
    apiKey: cfg.apiKey,            // typically empty for local Ollama
  });
}

```

This approach means adding a new provider requires only a thin wrapper around the shared HTTP utilities, not a bespoke integration.

## Unified Tool Execution Loop

The agent's runtime operates against the normalized schema in [`server/ai/drivers/http/toolArgs.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolArgs.ts), not against provider-specific SDKs. The **`runToolLoop`** function orchestrates tool calls by invoking **`executeAgentTool`** for operations like `insertHtml` or `setTokens`.

Because the tool loop consumes the standardized format, the same execution logic works across all providers. When a tool call arrives—whether from the UI or an external MCP client—[`executor.ts`](https://github.com/CoreBunch/Instatic/blob/main/executor.ts) validates the schema, runs the mutation against the live editor store, and returns an **`AiToolOutput`** shape (`{ ok: true, result }` or `{ ok: false, error }`).

## MCP Bridge for External Agents

Instatic exposes its tool ecosystem to external agents through the **Model-Client-Protocol (MCP)** bridge. When an admin editor mounts, the **`useEditorMcpBridge`** hook in [`src/admin/pages/site/agent/useEditorMcpBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/agent/useEditorMcpBridge.ts) opens a long-lived NDJSON stream at `/admin/api/ai/editor-bridge`.

External clients like Claude Code or Codex receive `toolRequest` events through this stream, execute the same `executeAgentTool` pipeline used by the native UI, and post results back via **`postToolResult`**. This makes the visual editor behave like a remote agent-enabled session, with the MCP layer handling provider-agnostic communication.

## Credential Management and Model Catalog

Provider credentials flow through [`server/auth/tokens.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/tokens.ts), which securely stores API keys and base URLs configured via `/admin/ai/providers`. All three drivers consume the same configuration shape (`{ apiKey, baseUrl, ... }`), enabling hot-swapping between providers without restarts.

After fetching raw model lists, the **`pricing`** module in `server/ai/pricing/*` enriches the catalog with pricing, context windows, and capability flags. This uniform catalog powers the UI's model picker, presenting Claude, GPT-4, and local Ollama models through a consistent interface.

## Implementation Examples

### Sending Chat Requests Across Providers

The **`getProviderAdapter`** function resolves the correct driver at runtime based on the provider ID:

```typescript
import { getProviderAdapter } from '@core/ai/drivers';
import { ChatMessage } from '@core/ai/runtime/types';

async function chatWithProvider(providerId: string, messages: ChatMessage[]) {
  const adapter = await getProviderAdapter(providerId); // resolves to Claude / OpenAI / Ollama
  const stream = await adapter.chatCompletions({
    model: 'claude-3-5-sonnet-20241022', // or any OpenAI model ID
    messages,
    temperature: 0.7,
  });

  for await (const event of stream) {
    console.log(event); // unified MessageStreamEvent shape
  }
}

```

*Implementation reference*: [`server/ai/drivers/http/chatCompletions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/chatCompletions.ts).

### Running an MCP Tool from an External Agent

Remote clients connect to the bridge to execute tools against the live editor:

```typescript
// Remote client (e.g., Claude Code) – pseudo-code
const bridge = new EventSource('/admin/api/ai/editor-bridge');
bridge.onmessage = async e => {
  const { type, requestId, toolName, input } = JSON.parse(e.data);
  if (type === 'toolRequest') {
    const result = await runTool(toolName, input); // same executor used locally
    await fetch(`/admin/api/ai/editor-bridge/${requestId}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(result),
    });
  }
};

```

*Local counterpart*: [`src/admin/pages/site/agent/useEditorMcpBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/agent/useEditorMcpBridge.ts) (client) ↔ [`src/admin/pages/site/agent/executor.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/agent/executor.ts) (server).

### Adding a New OpenAI-Compatible Provider

Because every driver ultimately speaks the OpenAI-Responses protocol, adding a new provider requires only a skeleton adapter:

```typescript
// Example skeleton for a new provider
import { ProviderAdapter } from '@core/ai/drivers/types';
import { makeChatCompletionsAdapter } from '@core/ai/drivers/http/chatCompletions';

export function createAdapter(cfg: ProviderConfig): ProviderAdapter {
  return makeChatCompletionsAdapter({
    baseUrl: cfg.baseUrl,          // OpenAI-compatible endpoint
    apiKey: cfg.apiKey,
    // provider-specific tweaks can be passed here
  });
}

```

No other part of the codebase needs to change—the `runToolLoop`, MCP bridge, and UI layers remain untouched.

## Summary

- **ProviderAdapter Interface**: The [`server/ai/drivers/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/types.ts) contract standardizes Claude, OpenAI, and Ollama behind a single OpenAI-Responses format.
- **Shared HTTP Layer**: [`server/ai/drivers/http/chatCompletions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/chatCompletions.ts) handles SSE streaming and request normalization for all providers via `makeChatCompletionsAdapter`.
- **Unified Tool Loop**: `runToolLoop` and `executeAgentTool` operate on normalized schemas in [`server/ai/drivers/http/toolArgs.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolArgs.ts), enabling provider-agnostic tool execution.
- **MCP Bridge**: `useEditorMcpBridge` exposes editor tools to external agents via NDJSON streams at `/admin/api/ai/editor-bridge`.
- **Credential Abstraction**: [`server/auth/tokens.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/tokens.ts) stores provider keys uniformly, allowing seamless switching between cloud and local models.
- **Driver Registration**: At startup, [`server/ai/drivers/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/index.ts) loads all registered drivers that export a `createAdapter(config)` function.

## Frequently Asked Questions

### How does Instatic normalize different LLM response formats?

Instatic translates all provider-specific responses into the **OpenAI-Responses** wire format through driver-specific adapters in `server/ai/drivers/`. Each adapter implements the `ProviderAdapter` interface, converting native schemas (Anthropic's message format, OpenAI's completions, Ollama's JSON) into a unified `MessageStreamEvent` shape that the tool loop consumes.

### Can I add a custom OpenAI-compatible provider to Instatic?

Yes. Create a new driver file that imports `makeChatCompletionsAdapter` from [`server/ai/drivers/http/chatCompletions.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/chatCompletions.ts) and returns an adapter configured with your base URL and API key. No changes are required to the `runToolLoop`, MCP bridge, or UI layers because they all consume the standardized interface defined in [`server/ai/drivers/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/types.ts).

### What is the MCP bridge used for in Instatic?

The MCP (Model-Client-Protocol) bridge enables external agents like Claude Code or Codex to control the Instatic editor remotely. When activated via `useEditorMcpBridge` in [`src/admin/pages/site/agent/useEditorMcpBridge.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/agent/useEditorMcpBridge.ts), it opens an NDJSON stream at `/admin/api/ai/editor-bridge` that emits `toolRequest` events. External agents can execute the same tool pipeline (`executeAgentTool`) used by the native UI and return results through `postToolResult`.

### Where are provider credentials stored in Instatic?

API keys and base URLs are stored securely via [`server/auth/tokens.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/auth/tokens.ts) and configured through the admin UI at `/admin/ai/providers`. All drivers receive credentials in the same shape (`{ apiKey, baseUrl, ... }`), allowing the system to instantiate Claude, OpenAI, or Ollama adapters interchangeably without modifying the credential storage logic.