> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leanmcp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Gateway Overview

> Route all your AI requests through a single proxy for complete visibility and control

export const CodeComparison = ({beforeCode, afterCode, title}) => {
  const [sliderPosition, setSliderPosition] = React.useState(50);
  const [isDarkMode, setIsDarkMode] = React.useState(false);
  const containerRef = React.useRef(null);
  const isDragging = React.useRef(false);
  React.useEffect(() => {
    const checkDarkMode = () => {
      const isDark = document.documentElement.classList.contains('dark') || document.body.classList.contains('dark') || document.documentElement.getAttribute('data-theme') === 'dark';
      setIsDarkMode(isDark);
    };
    checkDarkMode();
    const observer = new MutationObserver(checkDarkMode);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class', 'data-theme']
    });
    observer.observe(document.body, {
      attributes: true,
      attributeFilter: ['class', 'data-theme']
    });
    return () => observer.disconnect();
  }, []);
  const c = isDarkMode ? {
    bg: '#000000',
    codeBg: '#0a0a0a',
    border: '#1f1f1f',
    text: '#e5e5e5',
    muted: '#888888',
    addBg: 'rgba(34,197,94,0.2)',
    addText: '#4ade80',
    remBg: 'rgba(239,68,68,0.2)',
    remText: '#f87171',
    primary: '#3b82f6'
  } : {
    bg: '#ffffff',
    codeBg: '#f8fafc',
    border: '#e2e8f0',
    text: '#1e293b',
    muted: '#64748b',
    addBg: '#dcfce7',
    addText: '#15803d',
    remBg: '#fee2e2',
    remText: '#dc2626',
    primary: '#2563eb'
  };
  const parseCode = (code, type) => {
    if (!code) return [];
    return code.split('\n').map(line => {
      if (line.startsWith('+ ')) return type === 'after' ? {
        text: line.slice(2),
        t: 'add'
      } : null;
      if (line.startsWith('- ')) return type === 'before' ? {
        text: line.slice(2),
        t: 'rem'
      } : null;
      if (line.startsWith('  ')) return {
        text: line.slice(2),
        t: 'same'
      };
      return {
        text: line,
        t: 'same'
      };
    }).filter(Boolean);
  };
  const handleMove = x => {
    if (!containerRef.current) return;
    const rect = containerRef.current.getBoundingClientRect();
    setSliderPosition(Math.max(0, Math.min(100, (x - rect.left) / rect.width * 100)));
  };
  const beforeLines = parseCode(beforeCode, 'before');
  const afterLines = parseCode(afterCode, 'after');
  const lineStyle = t => ({
    display: 'block',
    padding: '3px 10px',
    margin: '1px 0',
    borderRadius: '4px',
    backgroundColor: t === 'add' ? c.addBg : t === 'rem' ? c.remBg : 'transparent',
    color: t === 'add' ? c.addText : t === 'rem' ? c.remText : c.text,
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
    fontSize: '13px',
    lineHeight: '1.6',
    whiteSpace: 'pre'
  });
  const renderLines = lines => lines.map((line, i) => <span key={i} style={lineStyle(line.t)}>{line.text || '\u00A0'}</span>);
  return <div ref={containerRef} style={{
    position: 'relative',
    borderRadius: '10px',
    border: `1px solid ${c.border}`,
    overflow: 'hidden',
    userSelect: 'none',
    cursor: 'ew-resize',
    backgroundColor: c.bg,
    margin: '24px 0',
    boxShadow: isDarkMode ? '0 4px 16px rgba(0,0,0,0.3)' : '0 2px 8px rgba(0,0,0,0.06)'
  }} onMouseMove={e => isDragging.current && handleMove(e.clientX)} onMouseUp={() => isDragging.current = false} onMouseLeave={() => isDragging.current = false} onTouchMove={e => handleMove(e.touches[0].clientX)} onTouchEnd={() => isDragging.current = false}>
            {}
            {title && <div style={{
    padding: '12px 16px',
    borderBottom: `1px solid ${c.border}`,
    backgroundColor: c.bg,
    fontSize: '13px',
    color: c.muted,
    fontFamily: 'ui-monospace, monospace',
    fontWeight: 500
  }}>
                    {title}
                </div>}

            {}
            <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    padding: '8px 16px',
    fontSize: '11px',
    fontWeight: 700,
    textTransform: 'uppercase',
    backgroundColor: c.bg,
    borderBottom: `1px solid ${c.border}`
  }}>
                <span style={{
    color: c.remText
  }}>Before</span>
                <span style={{
    color: c.addText
  }}>After</span>
            </div>

            {}
            <div style={{
    position: 'relative',
    backgroundColor: c.codeBg
  }}>
                {}
                <div style={{
    padding: '16px'
  }}>
                    {renderLines(afterLines)}
                </div>

                {}
                <div style={{
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    padding: '16px',
    backgroundColor: c.codeBg,
    clipPath: `inset(0 ${100 - sliderPosition}% 0 0)`,
    borderRight: `2px solid ${c.primary}`
  }}>
                    {renderLines(beforeLines)}
                </div>

                {}
                <div style={{
    position: 'absolute',
    top: 0,
    bottom: 0,
    width: '2px',
    backgroundColor: c.primary,
    cursor: 'ew-resize',
    zIndex: 10,
    left: `${sliderPosition}%`,
    transform: 'translateX(-50%)'
  }} onMouseDown={() => isDragging.current = true} onTouchStart={() => isDragging.current = true}>
                    <div style={{
    position: 'absolute',
    top: '50%',
    left: '50%',
    transform: 'translate(-50%, -50%)',
    width: '26px',
    height: '26px',
    borderRadius: '50%',
    backgroundColor: c.primary,
    border: '2px solid white',
    boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
  }}>
                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5">
                            <path d="M18 8L22 12L18 16" /><path d="M6 8L2 12L6 16" />
                        </svg>
                    </div>
                </div>
            </div>

            {}
            <div style={{
    textAlign: 'center',
    padding: '8px',
    fontSize: '11px',
    color: c.muted,
    backgroundColor: c.bg,
    borderTop: `1px solid ${c.border}`
  }}>
                Drag slider to compare
            </div>
        </div>;
};

# AI Gateway

One line change. Full visibility into every AI request — what was sent, what came back, how many tokens, what it cost.

Works with **Claude Code, Cursor, Windsurf, Cline, Raycast** and any app you build.

***

## How it works

You're changing one thing: where your AI requests go.

```
# before
ANTHROPIC_API_KEY=sk-ant-...   →   goes directly to Anthropic

# after
ANTHROPIC_AUTH_TOKEN=leanmcp_...   →   goes through LeanMCP gateway   →   then to Anthropic
```

Your tools behave identically. You get a full log of every request in [app.leanmcp.com/observability](https://app.leanmcp.com/observability).

<CodeComparison
  title="OpenAI SDK Integration"
  beforeCode={`import OpenAI from 'openai';

const client = new OpenAI({
- baseURL: 'https://api.openai.com/v1',
- apiKey: process.env.OPENAI_API_KEY,
});

const response = await client.chat.completions.create({
model: 'gpt-5',
messages: [{ role: 'user', content: 'Hello!' }],
});`}
  afterCode={`import OpenAI from 'openai';

const client = new OpenAI({
+ baseURL: 'https://aigateway.leanmcp.com/v1/openai',
+ apiKey: process.env.LEANMCP_API_KEY,
});

const response = await client.chat.completions.create({
model: 'gpt-5',
messages: [{ role: 'user', content: 'Hello!' }],
});`}
/>

***

## Why use AI Gateway?

<CardGroup cols={2}>
  <Card title="Complete Visibility" icon="eye">
    See exactly what data is being sent to AI providers from your tools and apps — full request body, response, tokens, and cost per request.
  </Card>

  <Card title="Security & Privacy" icon="shield">
    Automatically detect and block sensitive data — API keys, passwords, PII — before they reach an AI provider.
  </Card>

  <Card title="Cost Optimization" icon="chart-line">
    Track token usage per tool, per user, per feature. Run A/B tests on prompts and models to reduce spend.
  </Card>

  <Card title="Drop-in Replacement" icon="plug">
    Works with existing SDKs and tools. Change one URL and one env var — nothing else in your workflow changes.
  </Card>
</CardGroup>

***

## Supported providers

| Provider   | Gateway endpoint                      |
| ---------- | ------------------------------------- |
| Anthropic  | `aigateway.leanmcp.com/v1/anthropic`  |
| OpenAI     | `aigateway.leanmcp.com/v1/openai`     |
| xAI (Grok) | `aigateway.leanmcp.com/v1/xai`        |
| Fireworks  | `aigateway.leanmcp.com/v1/fireworks`  |
| ElevenLabs | `aigateway.leanmcp.com/v1/elevenlabs` |

***

## Where do you want to start?

<CardGroup cols={2}>
  <Card title="I use Claude Code, Cursor, or Windsurf" icon="laptop-code" href="/ai-gateway/for-personal-users">
    Set up the gateway for your personal coding tools. See exactly what your AI assistant is sending — your code, context, token usage, and cost — in real time.
  </Card>

  <Card title="I'm building an app that calls an AI API" icon="code" href="/ai-gateway/for-developers">
    Add the gateway to your backend. Get per-user tracking, abuse prevention, rate limiting, and cost controls in production.
  </Card>
</CardGroup>

***

## Quick setup (2 steps)

<Steps>
  <Step title="Get your API key">
    Top up credits at [app.leanmcp.com/billing](https://app.leanmcp.com/billing), then create a key at [app.leanmcp.com/api-keys](https://app.leanmcp.com/api-keys).
  </Step>

  <Step title="Point your tool at the gateway">
    Replace your provider's base URL and API key with your LeanMCP key. That's it.

    Pick your tool below for the exact config.
  </Step>
</Steps>

***

## Setup guides by tool

##### Personal tools

<CardGroup cols={3}>
  <Card title="Claude Code" href="/ai-gateway/claude-code" icon="terminal" />

  <Card title="Cursor" href="/ai-gateway/cursor" icon="pen-to-square" />

  <Card title="Windsurf" href="/ai-gateway/windsurf" icon="wind" />

  <Card title="Cline" href="/ai-gateway/cline" icon="plug" />

  <Card title="Raycast" href="/ai-gateway/raycast" icon="bolt" />

  <Card title="OpenCode" href="/ai-gateway/opencode" icon="square-terminal" />
</CardGroup>

##### Developers

<CardGroup cols={3}>
  <Card title="SDK Integration" href="/ai-gateway/sdk-integration" icon="brackets-curly">
    OpenAI and Anthropic SDK examples with user context headers.
  </Card>

  <Card title="LiteLLM" href="/ai-gateway/litellm" icon="arrows-split-up-and-left">
    Route LiteLLM calls through the gateway for full observability of tool calls and conversations.
  </Card>

  <Card title="Full Integration Guide" href="/guides/ai-gateway" icon="book">
    All providers, auth patterns, rate limiting, and A/B testing.
  </Card>
</CardGroup>

***

## What you'll see after setup

Go to [app.leanmcp.com/observability](https://app.leanmcp.com/observability) after your first request:

| Field         | What it shows              |
| ------------- | -------------------------- |
| Request Body  | Full prompt sent to the AI |
| Response Body | Full response received     |
| Model         | Which model was used       |
| Tokens        | Input / output / total     |
| Latency       | Response time in ms        |
| Status        | Success or error           |
| Timestamp     | When the request was made  |
