Someone asks an AI assistant, "find me a freelance Next.js developer and set up a call." The assistant finds my site. What happens next is usually a dead end: it summarises a landing page, then tells the human to go figure out the contact form. I decided that dead end was a bug, so I fixed it. The fix is three small pieces and one rule.
The front door: llms.txt
Websites have `robots.txt` for crawlers and `sitemap.xml` for search engines. Agents need something different: a short, plain-text briefing they can read in one fetch. That is /llms.txt. It states who I am, links every case study and blog post with a one-line description, and then does the part most sites skip: it tells the agent exactly what it is allowed to do and how.
It is generated from the same data as the rest of the site, so the post list and case list cannot drift out of date. Publish a new article and the agent-facing index updates with it.
Why capacity is not on the page
Whether I have a free project slot this month is a fact that changes weekly. Baking it into a statically built page means every visitor and every cached copy in an AI's memory is eventually wrong. So the pages describe me, and live facts come from a small ops database exposed three ways: a REST snapshot at `/api/v1/agent/ops`, an OpenAPI document at `/openapi.json`, and an MCP endpoint at `/api/mcp`.
| Path | Best for | How it works |
|---|---|---|
| MCP connector | Assistants that support tools | Call request_call with an email or phone. Name, timezone, preferred times, and project are optional. |
| REST API | Scripts and custom agents | POST JSON with email or phone to /api/v1/agent/bookings. |
| Prefilled link | Any assistant that can only write text | Give the human a /book#... URL. Their browser does the rest. |
The MCP server is stateless Streamable HTTP and exposes tools such as `get_ops_snapshot`, `get_capacity`, `list_positions`, `list_engagements`, `get_availability`, and `request_call`. An agent can answer "is he free in January?" from data, not from a guess.
The weird one: a link that does the work
The third path is my favourite because it works with the dumbest possible assistant. A chat model that can only output text can still output a URL:
https://vishu.app/book#name=Alex&email=alex%40gmail.com×=Tue%2010%3A00%20IST&project=Delivery%20app
Keys are `name`, `email`, `phone`, `times`, and `project`. All are optional, URL-encoded, and placed after the `#`. The human clicks, the form is already filled, and with an email present the request goes out immediately.
Why the # matters
Everything after `#` is the URL fragment. Browsers keep it client-side and do not include it in the HTTP request. So the name, email, and project description an assistant packs into the link never appear in my server access logs, a CDN log, or a referrer header. The page's own JavaScript reads it locally. For a link that carries a stranger's contact details, that is the right default.
The rule: opening a URL never books anything
Here is the part that took the most thought. Link previews, chat apps, security scanners, and AI browsing tools all open URLs on a person's behalf, sometimes several times. If opening a link could finalise a booking, a scanner would book calls for people who never asked. So I split the flow in two:
- Request. Opening the link (or calling the API or MCP tool) with an email records the request and sends a confirmation email to that address.
- Confirm. The call is booked only when the person clicks Confirm in that email.
This is double opt-in, the same pattern newsletters use, applied to a calendar. It means an assistant can act confidently for someone else ("book this for my friend") without ever being able to commit them. A phone-only link works differently: it opens the form with the number filled in and the person submits it themselves. Calls are held on weekdays, 09:30 to 13:30 IST.
Why bother
- Discovery is moving. A growing share of "who should I hire" starts in an assistant, not a search box. If the assistant cannot act on my site, it recommends someone it can act on.
- It is cheap. One text file, one route handler, one MCP endpoint. The data already existed.
- It is honest. Accurate, current facts in a format agents can cite beat hoping a model remembers a stale scrape.
- It is a demo. Anyone evaluating whether I can build agent-ready products can click the link and watch it work.
Steal this pattern
- Write a `/llms.txt` that says who you are and, explicitly, what an agent may do.
- Serve volatile facts from an endpoint, not from static pages.
- Offer one structured path (MCP or REST) and one dumb path (a prefilled link).
- Put user data in the URL fragment, not the query string.
- Never let a GET request commit anything. Make a human confirm out of band.
Want to try it? Read llms.txt, or just book a call. If you are building something similar and want it done properly, that is the kind of work in my case studies, and the same thinking drives natural-language database tools: give the model rails, not a blank check.