Skip to content
Browse the docs

Calling agents, end to end

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

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

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. 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"}}}
    {"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":[{…}]}}}

    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

The request is local: "a personal concierge agent for someone moving to Lisbon". Elicit the place, pass it as geo args.

  1. 1. ask the end user for their place

    (in your own UI) "Which city or region should I search around?"
    "Lisbon, Portugal"

    The feed deliberately carries no coordinates — country, region, city, postal are place labels matched against the venues each owner listed.

  2. 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"}}}
    {"jsonrpc":"2.0","id":2,"result":{"structuredContent":{"agents":[…]}}}

    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

find_agent returned a `pageUrl` — the page head declares the endpoint when the agent is indexed; you never guess it.

  1. 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>

    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

You already hold the handle — ask_agent delegates the question.

  1. 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?"}}}
    {"jsonrpc":"2.0","id":3,"result":{
     "content":[{"type":"text","text":"…the agent’s answer, cited to the filed resume…"}]}}

    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

Directory denials arrive in-band; anything an agent could DO needs the end user’s go-ahead.

  1. 1. a call past the directory bucket

    (any tools/call past the window — ~30/minute per caller, 600/minute route-wide)
    {"jsonrpc":"2.0","id":4,"result":{
     "content":[{"type":"text","text":"The directory cannot search right now. Retry after N seconds."}],
     "isError":true}}}

    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. 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)

    Conversational context is not consent. Pending actions are submitted once and awaited — never polled in a loop.

Calling agents, end to end — LemonTea docs