# For AI agents

This site answers AI agents. An MCP server at `https://ai.pournasserian.com/mcp` and a search API serve one index of every page, rebuilt each time the site deploys, so ChatGPT, Claude and any other client of the Model Context Protocol (MCP) can search and read it. The server is [mcp-my-site](https://pournasserian.com/open-source/mcp-my-site), the open-source tool I built, and this site is its first user; this page is about how this site runs it.

## Connect

Any MCP client that speaks the Streamable HTTP transport can connect to `https://ai.pournasserian.com/mcp`. No key is needed.

**Claude Code**

```bash
claude mcp add --transport http pournasserian https://ai.pournasserian.com/mcp
```

**Claude.ai and Claude Desktop.** Add `https://ai.pournasserian.com/mcp` as a custom connector in Settings.

**Visual Studio Code** (`.vscode/mcp.json`)

```json
{
  "servers": {
    "pournasserian": {
      "type": "http",
      "url": "https://ai.pournasserian.com/mcp"
    }
  }
}
```

**Cursor** (`.cursor/mcp.json`)

```json
{ "mcpServers": { "pournasserian": { "url": "https://ai.pournasserian.com/mcp" } } }
```

**In the browser.** The [playground](https://ai.pournasserian.com/playground) is a small MCP client in the page, so you can try the server without installing anything.

**curl**, to see the raw protocol:

```bash
curl -s https://ai.pournasserian.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"query":"mcp","limit":3}}}'
```

The server has two tools, both read-only. `search` takes a question or keywords and returns the pages that match best, one hit per page, each with an `id`, its title, its address, a snippet and the section it was found in. `fetch` takes that `id` and returns the whole page as Markdown, with its title, description, language and last-modified date. The server also gives the model instructions written from the site itself: what the site is, when its pages were indexed, when to use the tools, and an outline of its sections.

## Every door

The MCP server is one way in. Here is everything a machine can read on this site, and what each is for.

| Address | What it holds | Who it is for |
|---|---|---|
| `/llms.txt` | A map of the site: every page, article and idea by title and meta description, each linked to its Markdown copy | Language-model crawlers |
| `/llms-full.txt` | Every Markdown copy in one file, in the order of `llms.txt` | Language-model crawlers |
| A page's address with `.md` added (Home's is `/index.md`) | The page's Markdown copy, in its own words, named in the page's head by `rel="alternate"` | Agents |
| A page's own address, asked for with `Accept: text/markdown` | The same Markdown copy, for an agent that asks for Markdown without knowing the `.md` address | Agents |
| `/rss.xml` and `/ideas.xml` | The feeds of the articles and the ideas, newest first, each item carrying the whole post | Feed readers |
| `/sitemap.xml` | Every page, with the date it last changed | Search engines |
| Structured data (JSON-LD) in the pages | Who the site is about, and what a page is: a profile, a case study, a project or a post | Search engines |
| `/robots.txt` | Every crawler allowed, and Cloudflare's Content-Signal line: search, AI answers that cite the site and AI training are all welcome | Crawlers |
| `/.well-known/ai-catalog.json` | The AI Catalog, with the card of the MCP server; the server is also listed in the official MCP registry | Agents looking for the site's MCP server |
| `https://ai.pournasserian.com/mcp` | The MCP server, with its `search` and `fetch` tools | MCP clients |
| `https://ai.pournasserian.com/api/search?q=` | The search API, as JSON, described by an OpenAPI document at `/openapi/v1.json` and an interactive reference at `/docs` | Developers, and the site's own search box |
| `/search` | The site's own search box, which calls that API | People |

The Markdown copies, the feeds and `llms.txt` are built from the same files as the pages, so they hold the pages' own words.

## How the index is built

The index is rebuilt every time the site deploys. A push to master deploys the site as a Cloudflare Worker. GitHub Actions then checks out the pinned mcp-my-site release, crawls the live site, and embeds only the chunks that changed, through OpenRouter, with the embedding cache kept between runs. The index goes to Azure Blob Storage, the manifest last, so a reader never sees a half-written index. The server, on Azure Container Apps, polls the manifest and swaps the new index in without a restart.

```mermaid
flowchart TB
    Push["A push to master"] --> Site["The site deployed<br/>as a Cloudflare Worker"]
    Site --> Release
    subgraph Actions["GitHub Actions"]
        Release["The pinned mcp-my-site release"] --> Crawl["Crawl the live site"]
        Crawl --> Embed["Embed only the chunks that changed<br/>embedding cache kept between runs"]
    end
    Embed -- "through OpenRouter" --> Blob["Azure Blob Storage<br/>the index, the manifest last"]
    Release -. "a new server release, rolled out first" .-> Server
    Blob -. "polls the manifest" .-> Server["The server on Azure Container Apps<br/>swaps the new index in without a restart"]
```

When a new release is pinned, the server is rolled out before the index is uploaded, because an older server cannot read a newer index format, while a newer server reads the previous one.

A broken crawl never replaces a working index. The indexer refuses a crawl that found too few pages, lost more than half of them, or failed on too many, and nothing is uploaded; a server that cannot load a new index keeps serving the one it has. The server's embedding key and the certificate between Cloudflare and the server are held in Azure Key Vault.

## How a question is answered

Every request reaches the server through Cloudflare. The server reads the visitor's address from the `CF-Connecting-IP` header for its rate limit, and believes it only when the connection comes from Cloudflare's own ranges. The question is embedded through OpenRouter; keyword and vector search then run over the index and are fused per query, with one hit per page. When the embedding provider is unavailable, the search falls back to keywords alone and says so in its answer. The MCP tools and the search API share the same search code, so an agent and the site's search box are answered by the same search.

```mermaid
flowchart TB
    Agent["An MCP client<br/>search, fetch"] --> Edge
    Box["The site's search box<br/>GET /api/search"] --> Edge
    Edge["Cloudflare"] -- "CF-Connecting-IP" --> Server["The server<br/>rate limit per visitor"]
    Server --> Search["The same search code"]
    Search -- "the question is embedded" --> OpenRouter["OpenRouter"]
    OpenRouter --> Fused["Keyword and vector search<br/>fused per query"]
    Search -. "provider unavailable" .-> Keywords["Keywords alone"]
    Fused --> Hits["One hit per page"]
    Keywords --> Hits
```

## Cost and privacy

Embedding a small site costs a fraction of a cent, each rebuild re-embeds only the chunks whose text changed, and a question embeds a few tokens. The server's cost is its hosting plan.

What you search for is not logged. No log line or trace carries the text of a search unless the setting that turns it on is set, and this site leaves it off. The server's telemetry goes to Application Insights: searches by channel, how long they took and whether they fell back to keywords, never the text.

The MCP endpoint is open, with no key, and read-only: the tools cannot change anything, and they serve only what was public on the site anyway. Each visitor can make 60 requests a minute, shared between the MCP endpoint and the search API.

## What it is not

There is no chat yet: it is planned for the next version of mcp-my-site. Search runs on submit, with no search-as-you-type. Only HTML pages are indexed, and the server covers this site alone.

## Stack

mcp-my-site · Cloudflare Workers · GitHub Actions · Azure Container Apps · Azure Blob Storage · Azure Key Vault · Application Insights · OpenRouter
