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, 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

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)

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

Cursor (.cursor/mcp.json)

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

In the browser. The playground is a small MCP client in the page, so you can try the server without installing anything.

curl, to see the raw protocol:

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.

AddressWhat it holdsWho it is for
/llms.txtA map of the site: every page, article and idea by title and meta description, each linked to its Markdown copyLanguage-model crawlers
/llms-full.txtEvery Markdown copy in one file, in the order of llms.txtLanguage-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/markdownThe same Markdown copy, for an agent that asks for Markdown without knowing the .md addressAgents
/rss.xml and /ideas.xmlThe feeds of the articles and the ideas, newest first, each item carrying the whole postFeed readers
/sitemap.xmlEvery page, with the date it last changedSearch engines
Structured data (JSON-LD) in the pagesWho the site is about, and what a page is: a profile, a case study, a project or a postSearch engines
/robots.txtEvery crawler allowed, and Cloudflare’s Content-Signal line: search, AI answers that cite the site and AI training are all welcomeCrawlers
/.well-known/ai-catalog.jsonThe AI Catalog, with the card of the MCP server; the server is also listed in the official MCP registryAgents looking for the site’s MCP server
https://ai.pournasserian.com/mcpThe MCP server, with its search and fetch toolsMCP 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 /docsDevelopers, and the site’s own search box
/searchThe site’s own search box, which calls that APIPeople

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.

GitHub Actions: Crawl the live site, Embed only the chunks that changed embedding cache kept between runs; A push to master → The site deployed as a Cloudflare Worker; The site deployed as a Cloudflare Worker → The pinned mcp-my-site release; The pinned mcp-my-site release → Crawl the live site; Crawl the live site → Embed only the chunks that changed embedding cache kept between runs; Embed only the chunks that changed embedding cache kept between runs → Azure Blob Storage the index, the manifest last: through OpenRouter; The pinned mcp-my-site release → The server on Azure Container Apps swaps the new index in without a restart: a new server release, rolled out first; Azure Blob Storage the index, the manifest last → The server on Azure Container Apps swaps the new index in without a restart: polls the manifestGitHub ActionsA push to masterThe site deployedas a Cloudflare WorkerThe pinned mcp-my-site releaseCrawl the live siteEmbed only the chunks that changedembedding cache kept between runsAzure Blob Storagethe index, the manifest lastThe server on Azure Container Appsswaps the new index in without a restartthrough OpenRoutera new server release, rolled out firstpolls the manifest

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.

An MCP client search, fetch → Cloudflare; The site's search box GET /api/search → Cloudflare; Cloudflare → The server rate limit per visitor: CF-Connecting-IP; The server rate limit per visitor → The same search code; The same search code → OpenRouter: the question is embedded; OpenRouter → Keyword and vector search fused per query; The same search code → Keywords alone: provider unavailable; Keyword and vector search fused per query → One hit per page; Keywords alone → One hit per pageAn MCP clientsearch, fetchCloudflareThe site's search boxGET /api/searchThe serverrate limit per visitorThe same search codeOpenRouterKeyword and vector searchfused per queryKeywords aloneOne hit per pageCF-Connecting-IPthe question is embeddedprovider unavailable

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