# Connect a client

> Configuration for Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Codex, the Claude API, the OpenAI Responses API and custom code.

Every client needs the same two things:

| | |
|---|---|
| **Endpoint** | `https://mcp.foodperfect.ai/mcp` (Streamable HTTP) |
| **Header** | `Authorization: Bearer fp_live_…` ([create a key](/docs/authentication/)) |

Put the key in an environment variable (`FOODPERFECT_API_KEY` below) or your client's secret prompt rather than pasting it into a config file you might commit.

## Claude Code

```bash title="Terminal"
claude mcp add --transport http foodperfect https://mcp.foodperfect.ai/mcp \
  --header "Authorization: Bearer $FOODPERFECT_API_KEY"
```

Add `--scope project` to write it to the repo's `.mcp.json` for your team (each teammate then supplies their own key), or `--scope user` for all your projects. Check it with `claude mcp list`, or `/mcp` inside a session.

```json title=".mcp.json"
{
  "mcpServers": {
    "foodperfect": {
      "type": "http",
      "url": "https://mcp.foodperfect.ai/mcp",
      "headers": { "Authorization": "Bearer ${FOODPERFECT_API_KEY}" }
    }
  }
}
```

## Claude Desktop

Claude Desktop starts local servers, so it reaches FoodPerfect through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge (needs Node.js 18+). Open **Settings → Developer → Edit Config** and add:

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "foodperfect": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.foodperfect.ai/mcp", "--header", "Authorization:${FOODPERFECT_AUTH}"],
      "env": { "FOODPERFECT_AUTH": "Bearer fp_live_YOUR_KEY" }
    }
  }
}
```

Restart Claude Desktop. The value sits in `env` because some platforms mangle spaces inside `args`.

## Cursor

```json title="~/.cursor/mcp.json (or .cursor/mcp.json in a project)"
{
  "mcpServers": {
    "foodperfect": {
      "url": "https://mcp.foodperfect.ai/mcp",
      "headers": { "Authorization": "Bearer fp_live_YOUR_KEY" }
    }
  }
}
```

## VS Code (GitHub Copilot agent mode)

VS Code prompts for the key once and stores it securely:

```json title=".vscode/mcp.json"
{
  "inputs": [
    { "type": "promptString", "id": "foodperfect-key", "description": "FoodPerfect API key", "password": true }
  ],
  "servers": {
    "foodperfect": {
      "type": "http",
      "url": "https://mcp.foodperfect.ai/mcp",
      "headers": { "Authorization": "Bearer ${input:foodperfect-key}" }
    }
  }
}
```

## Windsurf

```json title="~/.codeium/windsurf/mcp_config.json"
{
  "mcpServers": {
    "foodperfect": {
      "serverUrl": "https://mcp.foodperfect.ai/mcp",
      "headers": { "Authorization": "Bearer fp_live_YOUR_KEY" }
    }
  }
}
```

## OpenAI Codex CLI

```toml title="~/.codex/config.toml"
[mcp_servers.foodperfect]
url = "https://mcp.foodperfect.ai/mcp"
bearer_token_env_var = "FOODPERFECT_API_KEY"
```

## Claude API (MCP connector)

Let Claude call FoodPerfect directly from the Messages API. Anthropic connects to the server for you.

```python tab="Python"
import os
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    betas=["mcp-client-2025-11-20"],
    mcp_servers=[{
        "type": "url",
        "url": "https://mcp.foodperfect.ai/mcp",
        "name": "foodperfect",
        "authorization_token": os.environ["FOODPERFECT_API_KEY"],
    }],
    tools=[{"type": "mcp_toolset", "mcp_server_name": "foodperfect"}],
    messages=[{"role": "user", "content": "What's my FoodPerfect balance and brand voice?"}],
)
print(response.content)
```

```bash tab="curl"
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-11-20" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 16000,
    "mcp_servers": [{
      "type": "url",
      "url": "https://mcp.foodperfect.ai/mcp",
      "name": "foodperfect",
      "authorization_token": "'"$FOODPERFECT_API_KEY"'"
    }],
    "tools": [{"type": "mcp_toolset", "mcp_server_name": "foodperfect"}],
    "messages": [{"role": "user", "content": "What is my FoodPerfect balance and brand voice?"}]
  }'
```

## OpenAI Responses API

```python title="Python"
from openai import OpenAI
import os

client = OpenAI()
response = client.responses.create(
    model="gpt-5",
    tools=[{
        "type": "mcp",
        "server_label": "foodperfect",
        "server_url": "https://mcp.foodperfect.ai/mcp",
        "headers": {"Authorization": f"Bearer {os.environ['FOODPERFECT_API_KEY']}"},
        "require_approval": "always",
    }],
    input="What's my FoodPerfect balance and brand voice?",
)
print(response.output_text)
```

## claude.ai, ChatGPT and other web apps

Connectors in claude.ai, the ChatGPT app and similar web products sign in with OAuth. FoodPerfect uses API keys for now, so connect through a desktop or coding client above, or from your own code.

## Custom code

Any HTTP client works. The server is stateless and answers with plain JSON, so you don't need an MCP SDK. See [Raw HTTP (JSON-RPC)](/docs/http/) for a small helper.

## Safe retries

Every client above can use every tool. If you call FoodPerfect from your own code and retry on network errors, send an `Idempotency-Key` header, unique per action, so a retry returns the first result instead of charging twice ([details](/docs/errors/#idempotency)).

> [!NOTE]
> Don't put a fixed `Idempotency-Key` in a client config. The key is remembered for 24 hours, so every later call to the same tool would be rejected or would replay the first result.

## Check the connection

Ask your agent to "call FoodPerfect's ping tool". A healthy server answers with its version. If it doesn't:

| Symptom | Likely cause |
|---|---|
| `401 unauthorized` | Key missing, mistyped, revoked or expired. Look it up in Settings → API Keys. |
| `403` "Origin is not allowed" | A browser-based client sent an `Origin` header the server doesn't accept. Use a desktop or server-side client. |
| Tools don't show up | Restart the client after editing its config; check the JSON is valid. |
| `unauthorized` on one tool only | The key's scopes don't include it. See [scopes](/docs/authentication/#scopes). |
