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

# MCP web-search server

> Call Tinfoil's confidential websearch MCP server directly over attested HTTP.

## Overview

The [`confidential-websearch`](https://github.com/tinfoilsh/confidential-websearch) MCP server runs inside a Tinfoil secure enclave and exposes two tools: `search` (web search via [Exa](https://exa.ai), a Zero Data Retention provider) and `fetch` (headless-browser page rendering via Cloudflare Browser Rendering). Both return results back into the enclave before being handed to the caller, so queries and page content are only ever decrypted inside attested code.

Use this guide when you want to drive the web-search tool loop yourself — for example, from a custom agent runtime or any MCP-compatible client. If you just want a model to search the web as part of a chat completion, prefer the higher-level [Web search guide](/guides/web-search) which wraps this same server behind `web_search_options` and the `web_search` Responses tool.

Optional safety filters run inside the same enclave:

* **PII filter** blocks outgoing search queries that contain sensitive identifiers before they reach Exa.
* **Prompt-injection filter** drops search results and fetched pages that contain instructions aimed at hijacking a downstream model.

Both filters are opt-in per request.

## Endpoint and transport

| Item            | Value                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------- |
| Production host | `websearch.tinfoil.sh`                                                                    |
| MCP endpoint    | `POST https://websearch.tinfoil.sh/mcp`                                                   |
| Health check    | `GET https://websearch.tinfoil.sh/health`                                                 |
| Transport       | MCP Streamable HTTP                                                                       |
| Source repo     | [`tinfoilsh/confidential-websearch`](https://github.com/tinfoilsh/confidential-websearch) |

Any MCP-compliant client can introspect the tool surface by calling `tools/list` on the endpoint.

## Authentication

Send your Tinfoil API key as a bearer token:

```
Authorization: Bearer $TINFOIL_API_KEY
```

This is an attested enclave. For connection-time trust guarantees, connect through a client that verifies the enclave's attestation and pins its TLS certificate or HPKE key — see [Calling from a Tinfoil SDK](#calling-from-a-tinfoil-sdk) below.

## Tools

### `search`

Run a web search and return ranked results with titles, URLs, snippets, and publication dates.

#### Arguments

| Name                    | Type      | Required | Default      | Description                                                                                                                                                                               |
| ----------------------- | --------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`                 | string    | yes      | -            | Natural language search query. Max \~400 characters.                                                                                                                                      |
| `max_results`           | int       | no       | `8`          | Number of results to return. The upstream provider applies its own ceiling.                                                                                                               |
| `content_mode`          | string    | no       | `highlights` | `highlights` returns key excerpts relevant to the query. `text` returns the full page text as markdown.                                                                                   |
| `max_content_chars`     | int       | no       | `700`        | Per-result character budget for the snippet or text returned in each hit.                                                                                                                 |
| `user_location_country` | string    | no       | -            | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `DE`) used to bias results toward that locale.                                                                                          |
| `allowed_domains`       | string\[] | no       | -            | Only return results whose host matches one of these domains.                                                                                                                              |
| `excluded_domains`      | string\[] | no       | -            | Drop results from these domains.                                                                                                                                                          |
| `category`              | string    | no       | -            | Restrict to one of: `company`, `people`, `research paper`, `news`, `personal site`, `financial report`. `company` and `people` are incompatible with date filters and `excluded_domains`. |
| `start_published_date`  | string    | no       | -            | ISO-8601 date. Only include results published at or after this instant.                                                                                                                   |
| `end_published_date`    | string    | no       | -            | ISO-8601 date. Only include results published at or before this instant.                                                                                                                  |
| `max_age_hours`         | int       | no       | -            | Cache freshness control. `0` forces a livecrawl on every result (freshest, slowest). `-1` disables livecrawl (cache-only, fastest). Omit for the upstream default.                        |

#### Response

```json theme={"dark"}
{
  "results": [
    {
      "title": "string",
      "url": "string",
      "content": "string",
      "favicon": "string (optional)",
      "published_date": "string (optional, ISO-8601)"
    }
  ]
}
```

### `fetch`

Fetch one or more web pages via Cloudflare Browser Rendering and return them as clean markdown. Use this after `search` when you need the full page beyond the returned snippet.

#### Arguments

| Name              | Type      | Required | Default | Description                                                                              |
| ----------------- | --------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
| `urls`            | string\[] | yes      | -       | One or more HTTP/HTTPS URLs. Capped at 20 per request.                                   |
| `allowed_domains` | string\[] | no       | -       | If set, reject any URL whose host is not in this list before it is sent to the renderer. |

The server rejects unsafe fetch targets before they reach Cloudflare (localhost, internal hostnames, private IP ranges, unsupported URL schemes).

#### Response

`results` preserves input order and includes both completed and failed fetches. `pages` is the convenience subset of `results` whose `status` is `completed`.

```json theme={"dark"}
{
  "pages": [
    { "url": "string", "content": "string (markdown)" }
  ],
  "results": [
    {
      "url": "string",
      "status": "completed | failed",
      "content": "string (present when status=completed)",
      "error": "string (present when status=failed)"
    }
  ]
}
```

## Per-request safety headers

The server's safety filters have env-configured defaults, but an integrator can override them on a single request by forwarding either header on the `POST /mcp` call. Missing, empty, or unparseable values fall back to the server default, so a malformed header cannot silently weaken filtering.

| Header                           | Values          | Effect                                                                                          |
| -------------------------------- | --------------- | ----------------------------------------------------------------------------------------------- |
| `X-Tinfoil-Tool-PII-Check`       | `true`, `false` | Override the PII filter on outgoing search queries for this request only.                       |
| `X-Tinfoil-Tool-Injection-Check` | `true`, `false` | Override the prompt-injection filter on search results and fetched pages for this request only. |

See [PII protection](/guides/web-search#pii-protection) and [Prompt-injection protection](/guides/web-search#prompt-injection-protection) in the Web search guide for what each filter blocks.

## Calling from a Tinfoil SDK

Because the websearch server is an attested enclave, you should verify its attestation before trusting traffic to it. Every Tinfoil SDK exposes a `SecureClient` that does this for you: it verifies the enclave's signed release against the `tinfoilsh/confidential-websearch` GitHub repo, pins the attested transport, and returns a verified HTTP client you can hand to any MCP client transport.

The example below pairs `tinfoil-js` with the TypeScript MCP SDK to call `search`. The same pattern works from the [Python](/sdk/python-sdk), [Go](/sdk/go-sdk), and [Swift](/sdk/swift-sdk) SDKs — point their `SecureClient` at `websearch.tinfoil.sh` with `configRepo = tinfoilsh/confidential-websearch` and plug the verified client into the MCP SDK of your choice.

```typescript theme={"dark"}
import { SecureClient } from "tinfoil";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const secure = new SecureClient({
  enclaveURL: "https://websearch.tinfoil.sh",
  configRepo: "tinfoilsh/confidential-websearch",
});
await secure.ready();

const transport = new StreamableHTTPClientTransport(
  new URL(secure.getBaseURL() + "/mcp"),
  {
    fetch: secure.fetch,
    requestInit: {
      headers: {
        Authorization: `Bearer ${process.env.TINFOIL_API_KEY}`,
      },
    },
  },
);

const client = new Client({ name: "websearch-example", version: "0.1.0" });
await client.connect(transport);

const result = await client.callTool({
  name: "search",
  arguments: {
    query: "confidential computing attestation 2026",
    max_results: 5,
  },
});

console.log(result);
```

If verification fails, `ready()` rejects before any request is sent. See the [JavaScript SDK guide](/sdk/javascript-sdk) for transport-mode options (EHBP vs. TLS pinning) and proxying.

## Limits and defaults

| Item                               | Value             |
| ---------------------------------- | ----------------- |
| `search.max_results` default       | 8                 |
| `search.max_content_chars` default | 700               |
| `fetch.urls` per-call cap          | 20                |
| Cloudflare per-URL timeout         | 60s               |
| Rendered markdown truncation       | 50,000 characters |

## See also

* [Web search guide](/guides/web-search) — the higher-level wrapper that drives this server for you on `/v1/chat/completions` and `/v1/responses`.
* [`tinfoilsh/confidential-websearch`](https://github.com/tinfoilsh/confidential-websearch) — source and release history for the MCP server itself.
* [JavaScript SDK](/sdk/javascript-sdk) — attested `SecureClient` used in the example above.
