Skip to main content

Overview

The confidential-websearch MCP server runs inside a Tinfoil secure enclave and exposes two tools: search (web search via Exa, 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 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

ItemValue
Production hostwebsearch.tinfoil.sh
MCP endpointPOST https://websearch.tinfoil.sh/mcp
Health checkGET https://websearch.tinfoil.sh/health
TransportMCP Streamable HTTP
Source repotinfoilsh/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 below.

Tools

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

Arguments

NameTypeRequiredDefaultDescription
querystringyes-Natural language search query. Max ~400 characters.
max_resultsintno8Number of results to return. The upstream provider applies its own ceiling.
content_modestringnohighlightshighlights returns key excerpts relevant to the query. text returns the full page text as markdown.
max_content_charsintno700Per-result character budget for the snippet or text returned in each hit.
user_location_countrystringno-ISO 3166-1 alpha-2 country code (e.g. US, GB, DE) used to bias results toward that locale.
allowed_domainsstring[]no-Only return results whose host matches one of these domains.
excluded_domainsstring[]no-Drop results from these domains.
categorystringno-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_datestringno-ISO-8601 date. Only include results published at or after this instant.
end_published_datestringno-ISO-8601 date. Only include results published at or before this instant.
max_age_hoursintno-Cache freshness control. 0 forces a livecrawl on every result (freshest, slowest). -1 disables livecrawl (cache-only, fastest). Omit for the upstream default.

Response

{
  "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

NameTypeRequiredDefaultDescription
urlsstring[]yes-One or more HTTP/HTTPS URLs. Capped at 20 per request.
allowed_domainsstring[]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.
{
  "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.
HeaderValuesEffect
X-Tinfoil-Tool-PII-Checktrue, falseOverride the PII filter on outgoing search queries for this request only.
X-Tinfoil-Tool-Injection-Checktrue, falseOverride the prompt-injection filter on search results and fetched pages for this request only.
See PII protection and 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, Go, and Swift 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.
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 for transport-mode options (EHBP vs. TLS pinning) and proxying.

Limits and defaults

ItemValue
search.max_results default8
search.max_content_chars default700
fetch.urls per-call cap20
Cloudflare per-URL timeout60s
Rendered markdown truncation50,000 characters

See also