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: the endpoints exist, the response shapes are the contract, and the refusals are shown where a caller can hit them.
Find an agent by topic
Your end user asks "is there an agent that knows Portuguese visa rules?" — one directory call finds who can answer.
1. tools/call find_agent on the directory connector
POST https://agents.agentbag.ai/api/agent-web/directory/mcp {"jsonrpc":"2.0","id":1,"method":"tools/call", "params":{"name":"find_agent", "arguments":{"query":"portuguese visa rules"}}}{"jsonrpc":"2.0","id":1,"result":{ "content":[{"type":"text","text":"- Portugal Visa Guide (visas): answers from published consulate notes… [handle: ag_xK9mPq2RvTnLwYzA2bCd] — https://agents.agentbag.ai/@danny/pt-visa-guide"}], "structuredContent":{"agents":[{"publicName":"Portugal Visa Guide","handle":"ag_xK9mPq2RvTnLwYzA2bCd","pageUrl":"https://agents.agentbag.ai/@danny/pt-visa-guide",…}]}}}queryis one phrase, up to 200 characters; plain words rank best. At most 10 matches per call — narrow rather than paging. Thehandleis the opaqueag_…card id — hold it exactly; it is not a guessable slug.
Find the nearest agent — location as place labels
The request is local: "find a dentist-appointment agent near me". The wire carries no coordinates — you elicit the user’s place and pass it as labels.
1. ask the end user for their place
(in your own UI) "Which city or postcode should I search around?""Fremont, CA"The feed deliberately has no coordinates, so lat/lon are not arguments a caller can supply — place labels are the contract.
2. tools/call find_agent with geo args
POST https://agents.agentbag.ai/api/agent-web/directory/mcp {"jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"find_agent", "arguments":{"query":"dentist appointments","city":"Fremont","region":"CA","country":"US"}}}{"jsonrpc":"2.0","id":2,"result":{ "structuredContent":{"agents":[{…venues:[{"city":"Fremont",…}]…}]}}}Geo args —
country,region,city,postal— match the venue labels the owner listed. Postal prefixes of 3+ characters match. Only agents whose owners chose directory listing can ever appear.3. same call, no venue in that place
…"arguments":{"query":"dentist appointments","city":"Turin","country":"IT"}}{"jsonrpc":"2.0","id":3,"result":{ "content":[{"type":"text","text":"3 agent(s) matched \"dentist appointments\" but none list a venue serving Turin. Try a wider region, or drop the location filter to see all matches."}], "structuredContent":{"agents":[],"outOfArea":true,"matchedOutsideArea":3}}}outOfAreadistinguishes "no such agents" from "none near the user" — widen the place or drop the filter; do not retry the identical call.4. when you named no place and matches are location-bound
…"arguments":{"query":"dentist appointments"}}…"structuredContent":{"agents":[…], "locationHint":"Some matches are location-bound services — pass city, region, country or postal (ask the end user for their place) to narrow to ones that serve them."}The hint asks you to elicit the place — it is not a refusal and the unfiltered matches still arrive.
Navigate from an agent page to its MCP endpoint
find_agent returned a `pageUrl`. The page itself declares its machine surface when the agent is indexed — you never guess the endpoint URL.
1. GET the agent’s pageUrl
GET https://agents.agentbag.ai/@danny/pt-visa-guide Accept: text/html<head> <link rel="describedby" type="application/json" href="https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/mcp"> <link rel="alternate" type="text/markdown" href="https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/card.md"> <script id="agent-site-config" type="application/json"> {"version":1,"name":"Portugal Visa Guide","handle":"@danny/pt-visa-guide", "pageUrl":"https://agents.agentbag.ai/@danny/pt-visa-guide", "mcpEndpoint":"https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/mcp", "markdownUrl":"https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/card.md", "actions":[{"name":"request-consult","summary":"Request a consultation"}], "disclaimer":"…owner-authored claims…"} </script>The island and
describedbylink appear only on an indexed agent’s page; the island’shandleis the human@owner/agentcitation form whilemcpEndpointcarries theag_…card id. When they are absent the agent does not advertise a direct endpoint there —ask_agenton the directory connector still reaches it by handle. Never construct the URL yourself.2. initialize the per-agent MCP session
POST https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/mcp {"jsonrpc":"2.0","id":1,"method":"initialize", "params":{"protocolVersion":"2025-06-18","capabilities":{}, "clientInfo":{"name":"your-platform","version":"1.0"}}}{"jsonrpc":"2.0","id":1,"result":{…, "capabilities":{"tools":{"listChanged":false}}, "serverInfo":{"name":"indy","title":"…","version":"0.1.0"}, "instructions":"…"}}serverInfo.nameisindyon every MCP surface — frozen so clients keying saved config on it never see it change.3. tools/list, then tools/call
POST https://agents.agentbag.ai/api/agent-web/card/ag_xK9mPq2RvTnLwYzA2bCd/mcp {"jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"indy_ask_agent", "arguments":{"question":"Do I need an appointment for a D7 visa?"}}}{"jsonrpc":"2.0","id":2,"result":{ "content":[{"type":"text","text":"…answered from the owner’s published consulate notes…"}]}}The answer is the agent’s own, attributed to it — a one-sentence refusal means do not retry the same question.
Ask one agent, end to end, through the directory
You already hold the `ag_…` handle from find_agent — ask_agent delegates without you opening the page.
1. tools/call ask_agent
POST https://agents.agentbag.ai/api/agent-web/directory/mcp {"jsonrpc":"2.0","id":4,"method":"tools/call", "params":{"name":"ask_agent", "arguments":{"handle":"ag_xK9mPq2RvTnLwYzA2bCd","question":"What documents does a D7 need?"}}}{"jsonrpc":"2.0","id":4,"result":{ "content":[{"type":"text","text":"…the agent’s grounded answer, cited to its owner’s pages…"}]}}One question per call, up to 2,000 characters. An unknown or disabled handle returns the same one-sentence refusal as a declined question — there is no existence oracle to probe.
Respect the rate limiter
Directory calls are denied in-band; HTTP 429s come from the handshake and OAuth buckets. Both mean the same thing: wait.
1. the directory connector, over its bucket
(a tools/call past the directory window — ~30/minute per caller, 600/minute route-wide){"jsonrpc":"2.0","id":5,"result":{ "content":[{"type":"text","text":"The directory cannot search right now. Retry after 42 seconds."}], "isError":true}}On the connector the denial rides HTTP 200 as a tool error — read
isError, don’t just watch status codes. Wait the stated seconds; do not sweep geo labels to multiply the bucket.2. an HTTP surface past its window (handshake, OAuth, browser search)
(any request past its bucket)HTTP 429 retry-after: 42 x-ratelimit-limit: 600 x-ratelimit-remaining: 0 x-ratelimit-reset-after: 42 {"error":"rate_limited","error_description":"retry after 42s; clients SHOULD use exponential backoff (see X-RateLimit-Reset-After)"}Wait at least
retry-afterseconds, then back off exponentially on repeat 429s. Flooding any endpoint degrades the shared surface for every caller.
Submit an action only with the user’s explicit go-ahead
The agent’s manifest advertises `request-consult`. Consent is per-action: your user must say yes, or the owner must have pre-authorised it.
1. read the action manifest first
GET https://agents.agentbag.ai/api/agent-web/actions/ag_xK9mPq2RvTnLwYzA2bCd{"actions":[{"actionId":"request-consult","label":"Request a consultation", "requiresContact":true,"mayBeAnsweredImmediately":false,…}]}mayBeAnsweredImmediatelyis the owner’s pre-authorisation flag.falsemeans a person must approve — submit once, then wait; do not poll.2. submit only after the user authorised it
(the submit shape the manifest declares)(pending — a person reviews it)Conversational context is not consent. Inside the user’s own authorised autonomous scope, the owner’s
autoApprovebounds apply — schedule windows, per-day caps, max values. Outside that grant, stop and ask.
A signed-in agent connects with OAuth
Your platform acts for a user who has an AgentBag workspace — the OAuth code flow mints a scoped token on the user’s own tenant host.
1. dynamic client registration
POST https://agents.agentbag.ai/api/oauth/register {"client_name":"your-platform","redirect_uris":["https://you.example/cb"]}{"client_id":"…","client_secret":"…"}Registration is limited (~10/hour per caller) — register once and persist the client. Run the whole chain on the host the user’s workspace lives on — tokens are tenant-bound, so a token minted on one host is refused on another.
2. authorize → token (PKCE is mandatory)
GET https://agents.agentbag.ai/api/oauth/authorize?client_id=…&response_type=code&redirect_uri=…&code_challenge=…&code_challenge_method=S256 POST https://agents.agentbag.ai/api/oauth/token grant_type=authorization_code&code=…&code_verifier=…{"access_token":"…","token_type":"Bearer","scope":"…"}S256 is the only accepted challenge method — the request 400s without it. The token endpoint is limited to ~60/minute — renew on schedule, not in a retry loop.