---
title: MCP server
description: Connect Claude Code, Cursor, Claude Desktop or any MCP client to the data tools and the browser agent with one URL and your API key.
---

The Lovely Router MCP server lets an agent plan a task, find a tool, read its input schema, run it and read the results on its own. It also exposes the [browser agent](/docs/browser-agent). Every call goes through the same API as REST, so billing, limits and errors are identical.

| Setting | Value |
| --- | --- |
| URL | `https://lovelyrouter.com/mcp` |
| Transport | Streamable HTTP (JSON-RPC over `POST`, JSON responses) |
| Auth | `Authorization: Bearer lr-...`, the same key as the REST API |

## Connect your client

Create a key in [API keys](/app/keys) first. A key with a spend limit is a good idea for agents: see [control cost](#control-cost).

:::tabs
:::tab Claude Code
Add the server with one command:

```bash
claude mcp add --transport http lovely-tools https://lovelyrouter.com/mcp \
  --header "Authorization: Bearer $LOVELY_API_KEY"
```

This adds it to the current project only. Add `--scope user` to use it in every project. Run `claude mcp list` to check that it connects.

To share the server with your team, commit a `.mcp.json` at the project root instead. Claude Code expands `${LOVELY_API_KEY}` from each person's environment, so the key stays out of the file:

```json
{
  "mcpServers": {
    "lovely-tools": {
      "type": "http",
      "url": "https://lovelyrouter.com/mcp",
      "headers": { "Authorization": "Bearer ${LOVELY_API_KEY}" }
    }
  }
}
```
:::
:::tab Cursor
Add the server to `~/.cursor/mcp.json` for every project, or to `.cursor/mcp.json` in one project:

```json
{
  "mcpServers": {
    "lovely-tools": {
      "url": "https://lovelyrouter.com/mcp",
      "headers": { "Authorization": "Bearer lr-..." }
    }
  }
}
```

Replace `lr-...` with your key. The server then appears in Cursor's MCP settings, where you can check that it is connected.
:::
:::tab Claude Desktop
Claude Desktop starts local servers from its config file, so connect through the `mcp-remote` bridge. It needs Node.js.

Open **Settings → Developer → Edit Config** and add:

```json
{
  "mcpServers": {
    "lovely-tools": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://lovelyrouter.com/mcp", "--header", "Authorization:${LOVELY_AUTH}"],
      "env": { "LOVELY_AUTH": "Bearer lr-..." }
    }
  }
}
```

Replace `lr-...` with your key and restart Claude Desktop. Keep `Authorization:${LOVELY_AUTH}` without a space: some platforms split arguments on spaces.
:::
:::tab Other clients
Any client that supports remote MCP servers over Streamable HTTP works. Give it the URL and the header. The exact config keys depend on the client; a common shape is:

```json
{
  "mcpServers": {
    "lovely-tools": {
      "type": "http",
      "url": "https://lovelyrouter.com/mcp",
      "headers": { "Authorization": "Bearer lr-..." }
    }
  }
}
```

For clients that only start local servers, use the `mcp-remote` bridge shown in the Claude Desktop tab.
:::
:::

### Test the connection

The server speaks plain JSON-RPC, so you can call it with cURL. This lists the tools it exposes:

```bash title="cURL"
curl https://lovelyrouter.com/mcp \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
```

## Tools the server exposes

Two tools search and read the web. The server tells the agent to use them for anything it needs to look up or read, and to keep the browser agent for pages it has to interact with.

| Tool | What it does | Arguments |
| --- | --- | --- |
| `web_search` | Searches the web: up to 50 results, each with title, URL, date and a summary written for a model. Operators in the query: `site:`, `-site:`, `"exact phrase"`, `-word`, `lang:`, `after:`, `before:`. $0.0009 per search; a search with no results costs nothing. | `query` (required), `count`, `need_content`, `question` |
| `web_fetch` | Reads one page as clean Markdown: title and text, no menus or ads. $0.0009 per page; a page that can't be read costs nothing. A long page comes in parts, and reading on is free. | `url`, or `run_id` and `offset` to read on |

Two tools make images with [GPT Image 2.5](/docs/models/images), or GPT Image 2 when 2.5 is unavailable, billed like the Images API: $0.04 per image, any size, nothing when it fails.

| Tool | What it does | Arguments |
| --- | --- | --- |
| `generate_image` | Makes an image from a description, optionally from reference images (URLs, `data:` URLs or an earlier image's link). Answers with a download link that works for 24 hours without a key, and the `curl` command that saves it. An image not finished within the wait comes back with its job id. | `prompt` (required), `size`, `images` |
| `get_image` | Waits for an image that was still being made, then returns its link. Free. | `job_id` (required) |

Five tools cover the data catalog. The server tells the agent to plan with `suggest_tools` (or `search_tools` for a single tool), read the schema with `get_tool`, then call `run_tool`.

| Tool | What it does | Arguments |
| --- | --- | --- |
| `suggest_tools` | Plans a task: the tool to run first, an optional second tool and alternatives, each with an example input and an estimated cost. Free. | `task` (required), `limit` |
| `search_tools` | Keyword search over the catalog. Returns tool ids with prices. Free. | `query` (required), `limit` |
| `get_tool` | A tool's input schema, example input and price. Field descriptions carry the tool's query syntax, such as the X search operators. Free. | `tool` (required) |
| `run_tool` | Runs a tool and returns its results, billed from your balance. | `tool` (required), `input` (required), `max_results`, `fields` |
| `get_run` | Status and results of a run. Waits for it to finish. | `run_id` (required), `offset`, `fields` |

Four tools drive the [browser agent](/docs/browser-agent). It is for pages the agent has to interact with: logins, forms, multi-step flows. To read what a page says, `web_fetch` is faster and cheaper. The browser runs in the cloud and can't open `localhost` or private addresses.

| Tool | What it does | Arguments |
| --- | --- | --- |
| `browser_run` | Starts a browser run from a goal and a start URL, and returns its state after about 45 seconds. | `goal` (required), `start_url` (required), `mode`, `max_steps`, `output_schema`, `model`, `secrets`, `profile`, `save_profile`, `files`, `approve`, `human_timeout_minutes` |
| `browser_status` | A browser run's status. Waits up to about 45 seconds for progress. | `run_id` (required) |
| `browser_reply` | Answers a run waiting for a person: an `answer` to its question, `approve` true or false (with an optional `reason`), or `done` after a takeover. | `run_id` (required), one of `answer`, `approve`, `done` |
| `browser_takeover` | Hands the browser to the user now. The run's status then carries a live view URL. | `run_id` (required), `instructions` |

Each browser argument is described in the browser agent guides: [modes](/docs/browser-agent/modes), [structured output](/docs/browser-agent/structured-output), [logins and profiles](/docs/browser-agent/logins), [files](/docs/browser-agent/files) and [human in the loop](/docs/browser-agent/human-in-the-loop). Passwords and one-time codes are not sent through MCP: the agent uses vault secrets by name, and a person sends codes through the API.

### What the tools return

Answers are written for a model to read, and kept short, because agents cut off or spill long tool results:

- **`web_search`** returns numbered results with the title, site, date, URL and summary. **`web_fetch`** returns the page in parts of about 12,000 characters, with the `run_id` and `offset` that read on.
- **`run_tool` and `get_run`** return a line with the run id, tool, status, result count and charge, then one line per result with empty fields left out and long text cut. That fits about 12,000 characters per answer, and the last line says how to get the rest: `get_run` with an `offset`, or `fields` to keep only the keys you need. The full JSON stays available from [`GET /v1/tools/runs/{run_id}`](/docs/api/tools#retrieve-a-run).
- **The other tools** return the same JSON as the matching REST endpoint, as text: [`POST /v1/tools/suggest`](/docs/api/tools#plan-a-task), [`POST /v1/tools/discover`](/docs/api/tools#search-tools) and [`GET /v1/tools/{id}`](/docs/api/tools#retrieve-a-tool).
- **Errors** come back as a tool result with `isError: true` and the message, so the agent can adjust. When `run_tool` rejects an input, the answer lists the tool's input fields and an example.
- **`generate_image` and `get_image`** return a line with the job id, size, file size and charge, the download link and a `curl` command for it. The job is the same as one made with [`POST /v1/images/jobs`](/docs/models/images#jobs), so it shows in your logs and its image is also at `GET /v1/images/jobs/{id}/content` with your key for 7 days.
- **Long runs.** `run_tool` waits up to 50 seconds, because most clients give up on a tool call after about a minute. A run still going after that comes back as `Still running. Call get_run with this run id to get the results.` followed by the run. A client that allows longer tool calls can add `?wait=` to the server URL (10 to 280 seconds).

### Without a key

`suggest_tools`, `search_tools` and `get_tool` work without a key, so an agent can browse the catalog before you connect a key. `run_tool`, `get_run` and the browser tools return an authentication error until you add one.

## What an agent session looks like

Ask Claude Code, with the server connected: *"Find vegan restaurants in Austin and tell me what their reviews complain about."* A typical session:

1. **Plan.** The agent calls `suggest_tools` with the task. The plan says to run `google-maps-places` first, then `google-maps-reviews`, for an estimated $0.0585 at the default caps.
2. **Read the schema.** It calls `get_tool` with `{"tool": "google-maps-places"}` to see the input fields.
3. **Find the places.** It calls `run_tool`:

   ```json
   {
     "tool": "google-maps-places",
     "input": {"searchTerms": ["vegan restaurant"], "location": "Austin, Texas, USA"},
     "max_results": 10
   }
   ```

   Ten places come back with their `placeId`s. Cost: $0.00075 + 10 × $0.000238 = $0.00313.
4. **Get the reviews.** It calls `run_tool` again with the place ids, five reviews per place:

   ```json
   {
     "tool": "google-maps-reviews",
     "input": {"placeIds": ["ChIJ...", "ChIJ..."], "maxReviews": 5, "reviewsSort": "newest"},
     "max_results": 50
   }
   ```

   Fifty reviews come back. Cost: 50 × $0.000675 = $0.03375.
5. **Answer.** The agent reads the reviews and summarizes the complaints. The whole task cost about $0.08, and both runs appear in [Tools](/app/tools) in your dashboard.

Prices above are today's. `get_tool` and `suggest_tools` always return the live price.

## Control cost {#control-cost}

- **Give the agent its own key with a spend limit.** A run that could take the key past its limit is refused with `402` before anything is charged. See [API keys](/docs/account/api-keys).
- **Ask for `max_results` on every run.** The cap bounds the cost: per-call fee plus per-result fee times `max_results`. Without it, the tool's default applies. A line in your prompt or project instructions works: *"Always set max_results, and ask me before any run that can cost more than $0.50."*
- **Plan first.** `suggest_tools` and `get_tool` are free and show the price, so the agent can check a run's maximum cost before it starts.
- **Review spend.** Every run and its charge is listed in [Tools](/app/tools). Failed runs that return nothing cost nothing.

At most 10 runs can be in progress on your account at once. See [Run a tool](/docs/tools/run#billing) for how runs are billed.
