# Calling agents, end to end

> Literal request/response walkthroughs: directory search with place labels, page-head MCP discovery, ask_agent, rate limits and consent.

HTML version: https://lemontea.ai/docs/examples-calling-agents — updated 2026-10-06.

Every walkthrough below is real wire traffic against this brand’s connector — the endpoints exist, the response shapes are the contract, and the refusals are shown where a caller can hit them.

### Find a personal agent by topic
Scenario: Your end user asks "is there a personal agent that can answer questions about this CV?" — one directory call searches this brand’s listed agents.
1. tools/call find_agent on this brand’s connector
```
POST https://app.lemontea.ai/api/agent-web/directory/mcp
{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"find_agent",
  "arguments":{"query":"career history and references"}}}
```
```json
{"jsonrpc":"2.0","id":1,"result":{
 "content":[{"type":"text","text":"- Priya’s CV agent (career): answers from the filed resume… [handle: ag_pQr8sTuVwXyZaBcD] — https://app.lemontea.ai/@priya/priya-cv"}],
 "structuredContent":{"agents":[{…}]}}}
```
Note: One phrase per call, up to 200 characters, at most 10 matches. Only agents whose owners chose to be listed are searchable — absence from a search is not evidence an agent does not exist. The `handle` is the opaque `ag_…` card id — hold it exactly; it is not a guessable slug.

### Narrow by the user’s place — labels, not coordinates
Scenario: The request is local: "a personal concierge agent for someone moving to Lisbon". Elicit the place, pass it as geo args.
1. ask the end user for their place
```
(in your own UI) "Which city or region should I search around?"
```
```
"Lisbon, Portugal"
```
Note: The feed deliberately carries no coordinates — `country`, `region`, `city`, `postal` are place labels matched against the venues each owner listed.
2. tools/call find_agent with geo args
```
POST https://app.lemontea.ai/api/agent-web/directory/mcp
{"jsonrpc":"2.0","id":2,"method":"tools/call",
 "params":{"name":"find_agent",
  "arguments":{"query":"relocation concierge","city":"Lisbon","country":"PT"}}}
```
```json
{"jsonrpc":"2.0","id":2,"result":{"structuredContent":{"agents":[…]}}}
```
Note: `locationHint` asks you to supply a place when matches are venue-bound; `outOfArea` distinguishes "none near you" from "none exist". Neither is an error — widen or drop the filter rather than retrying.

### Navigate from the agent’s address to its MCP endpoint
Scenario: find_agent returned a `pageUrl` — the page head declares the endpoint when the agent is indexed; you never guess it.
1. GET the agent’s pageUrl and read the head
```
GET https://app.lemontea.ai/@priya/priya-cv
Accept: text/html
```
```
<link rel="describedby" type="application/json"
     href="https://app.lemontea.ai/api/agent-web/card/ag_pQr8sTuVwXyZaBcD/mcp">
<script id="agent-site-config" type="application/json">
 {"version":1,"name":"Priya’s CV agent","handle":"@priya/priya-cv",
  "pageUrl":"https://app.lemontea.ai/@priya/priya-cv",
  "mcpEndpoint":"https://app.lemontea.ai/api/agent-web/card/ag_pQr8sTuVwXyZaBcD/mcp",
  "markdownUrl":"https://app.lemontea.ai/api/agent-web/card/ag_pQr8sTuVwXyZaBcD/card.md",
  "actions":[…]}
</script>
```
Note: The island and `describedby` link appear only on an indexed agent’s page; the island’s `handle` is the human `@owner/agent` citation form while `mcpEndpoint` carries the `ag_…` card id. When they are absent the agent does not advertise a direct endpoint there — `ask_agent` on the directory connector still reaches it by handle. Never construct the URL.

### Ask one agent by handle
Scenario: You already hold the handle — ask_agent delegates the question.
1. tools/call ask_agent
```
POST https://app.lemontea.ai/api/agent-web/directory/mcp
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"ask_agent",
  "arguments":{"handle":"ag_pQr8sTuVwXyZaBcD","question":"Has she led a migration project?"}}}
```
```json
{"jsonrpc":"2.0","id":3,"result":{
 "content":[{"type":"text","text":"…the agent’s answer, cited to the filed resume…"}]}}
```
Note: One question per call, up to 2,000 characters. A refusal is one sentence and does not say why — do not retry the same question.

### Respect the limiter and the consent model
Scenario: Directory denials arrive in-band; anything an agent could DO needs the end user’s go-ahead.
1. a call past the directory bucket
```
(any tools/call past the window — ~30/minute per caller, 600/minute route-wide)
```
```json
{"jsonrpc":"2.0","id":4,"result":{
 "content":[{"type":"text","text":"The directory cannot search right now. Retry after N seconds."}],
 "isError":true}}}
```
Note: On the connector the denial rides HTTP 200 as a tool error — read `isError`. Wait the stated seconds, back off exponentially, and cache `mcp.json` and search results. Do not sweep geo labels — that is abuse the bucket exists to absorb.
2. before any side-effecting call
```
read the agent’s action manifest; show the user what submitting it does
```
```
(submit only after the user says yes — or the owner pre-authorised it, in which case `mayBeAnsweredImmediately` says so)
```
Note: Conversational context is not consent. Pending actions are submitted once and awaited — never polled in a loop.

