AgentCore Registry: The Missing Yellow Pages for AI Agents
14th April 2026
How we stopped hardcoding ARNs, what we learned publishing an MCP server and an A2A agent, and the VPC-endpoint footgun that shipped into every team’s first demo.
The problem you don’t notice until you have three agents
Your first agent is easy. You deploy it to AgentCore Runtime, get an ARN back, paste it into the frontend’s config.ts, ship.
const AGENT_ARN = "arn:aws:bedrock-agentcore:us-east-1:xxxxxx:runtime/agui_document_agent-TkV7qW3xrw";
const MCP_ARN = "arn:aws:bedrock-agentcore:us-east-1:xxxxxx:runtime/mcp_tools_server-ybvc8o7Rpi";
Your second agent is fine. Your third agent pulls tools from a teammate’s Gateway, which pulls tools from another team’s Lambda. Now a frontend config has five ARNs, a CI job maintains a sixth, and nobody knows which version of the refund-analytics-server is “the good one.” A new hire asks: “is there an agent that can do X?” and the honest answer is “grep our Slack.”
This is the problem the AgentCore Registry exists to solve. It’s a discovery catalog — a cross-account, cross-team index of the agents, MCP servers, skills, and other resources your organization has built. Think npm, DockerHub, or the Yellow Pages for AI building blocks.
What it is not is another runtime, another gateway, or another proxy. The registry does not execute anything. It stores pointers with rich metadata, makes them searchable (including semantically, via a hybrid LLM + keyword engine), and gates publication behind an approval workflow so garbage can’t flood the catalog.
Registry record vs ARN: different layers of the same stack
The first mental model that tripped us up was assuming the registry was “just another way to reference an agent.” It’s not. ARNs and registry records answer different questions.
ARN = "Where is this thing?" (address)
Record = "What is this thing and why would I use it?" (listing)
An ARN is a private identifier issued automatically when you deploy a runtime. It has no description, no schema, no owner, no version metadata, no search, no approval state. It’s the IP address of an agent — useful once you already know the agent exists.
A registry record wraps that ARN with everything a stranger would need to decide to use it:
| ARN | Registry record | |
|---|---|---|
| Created by | Runtime deploy | You, explicitly |
| Contents | Just an ID | Rich metadata + schemas + pointer to ARN |
| Searchable | No | Yes — semantic + keyword |
| Discoverable by other agents | No (must be told) | Yes — via MCP endpoint |
| Governance | IAM only | IAM + approval + deprecation |
| Versioning | Runtime versions only | Record versions + lifecycle state |
| Analogy | IP address | DNS entry + Yellow Pages listing |
If you’ve only ever built one agent, you don’t need a registry. If you’re in an org where someone else might want to use what you built — or where you want your agent to discover what someone else built — you do.
The four record types (it’s not just MCP)
A common first guess: “it’s a registry for MCP servers, right?” Half-right. There are four descriptorType values, and each models a different building block:
| Type | What lives here | Example |
|---|---|---|
| MCP | An MCP server + its tool list | A finance-tracker server with add_expense, list_expenses, summarize_spending |
| A2A | An agent-to-agent card (an agent’s public profile) | A document-authoring agent with skills research_topic, update_document |
| Agent Skills | Reusable skill definitions + markdown | A “refund-processing” skill with input/output schemas |
| Custom | Any schema you invent | Internal prompt templates, eval suites, dataset pointers |
The first three are protocol-specific — they assume you’re following either the Model Context Protocol (MCP) or Agent-to-Agent (A2A) spec. Custom is an escape hatch for anything that doesn’t fit: a REST API that’s not MCP, a Lambda function, a Bedrock knowledge base, a prompt library.
Most of AWS’s own samples use Custom because the MCP and A2A schemas are strict, and Custom lets you move fast while you figure out your shape.
Building it live: create a registry, publish two records, search them
Enough theory. Here’s the full workflow we ran, start to finish, for the agentcore-aigi project.
Step 1 — Create the registry
aws bedrock-agentcore-control create-registry \
--name agentcore_agui_demo_registry \
--description "Catalog for AgentCore AG-UI demo: MCP tools server + document agent" \
--authorizer-type AWS_IAM \
--region us-east-1
Returns:
{
"registryArn": "arn:aws:bedrock-agentcore:us-east-1:xxxxxxxx:registry/U7fQe0ZSCr5zdBBw"
}
Two choices matter here:
authorizerType is either AWS_IAM or CUSTOM_JWT. We used AWS_IAM because it needs zero setup — any IAM principal with the right policy can search the registry. CUSTOM_JWT plugs in your corporate OIDC provider (we’d point it at the same Cognito pool already used elsewhere in the stack) and lets end-users search the registry with their own tokens. That’s the right choice for production frontends; IAM is the right choice for backends and build systems.
approvalConfiguration.autoApproval defaults to false, meaning every new record starts as DRAFT, moves to PENDING_APPROVAL when submitted, and only becomes searchable after a human (or automation) approves it. That’s useful for a real team. For seeding demos, set autoApproval: true.
Step 2 — Publish the MCP record
The record schema took us three attempts to figure out. The CLI says the inlineContent fields must “conform to the MCP protocol specification” — which sounds like the full MCP server.json spec. It isn’t. AWS expects a minimal server descriptor and a specific protocol version:
SERVER='{
"name":"com.agentcore-demo/finance-tracker-mcp",
"description":"Stateful MCP server for personal finance tracking with elicitation and sampling",
"version":"1.0.0"
}'
TOOLS='{
"tools":[
{"name":"add_expense","description":"Record a new expense","inputSchema":{"type":"object","properties":{"amount":{"type":"number"},"category":{"type":"string"}},"required":["amount","category"]}},
{"name":"list_expenses","description":"List recorded expenses","inputSchema":{"type":"object","properties":{"category":{"type":"string"}}}},
{"name":"summarize_spending","description":"Summarize spending over a window","inputSchema":{"type":"object","properties":{"days":{"type":"integer"}}}}
]
}'
aws bedrock-agentcore-control create-registry-record \
--registry-id U7fQe0ZSCr5zdBBw \
--name finance_tracker_mcp \
--description "MCP server with expense tracking tools. Supports elicitation for missing fields." \
--descriptor-type MCP \
--descriptors "{
\"mcp\":{
\"server\":{\"schemaVersion\":\"2025-12-11\",\"inlineContent\":$(echo $SERVER | jq -Rs .)},
\"tools\":{\"inlineContent\":$(echo $TOOLS | jq -Rs .)}
}
}" \
--record-version "1.0.0" \
--region us-east-1
The gotchas, in order of painful discovery:
schemaVersion: "2025-12-11"— not the one in the public MCP spec docs. We found it only by readingawslabs/agentcore-samplesnotebooks.- Minimal server body — just
{name, description, version}. Addingcapabilities,remotes,endpoint, etc. (all valid per MCP’sserver.json) fails validation. - Tools wrapper is
{"tools": [...]}— not just an array. inlineContentis a JSON string, not a JSON object. Every example we tried to pass as a nested object got rejected. The whole thing has to be stringified then embedded.jq -Rs .handles the escaping.
Step 3 — Publish the A2A record
The A2A record carries an agent card — the A2A protocol’s equivalent of a service descriptor:
CARD='{
"protocolVersion":"0.3.0",
"name":"AG-UI Document Agent",
"description":"Strands-powered agent with AG-UI streaming. Reads documents, queries finance data via MCP, supports elicitation.",
"url":"bedrock-agentcore:us-east-1:xxxxxxx:runtime/agui_document_agent-TkV7qW3xrw",
"version":"1.0.0",
"capabilities":{"streaming":true},
"defaultInputModes":["text"],
"defaultOutputModes":["text"],
"skills":[
{"id":"query_finance","name":"Query Finance Tools","description":"Invoke MCP finance-tracker tools via stateful session","tags":["mcp","finance"]},
{"id":"document_qa","name":"Document Q&A","description":"Answer questions grounded in provided documents","tags":["rag","docs"]}
]
}'
aws bedrock-agentcore-control create-registry-record \
--registry-id U7fQe0ZSCr5zdBBw \
--name agui_document_agent \
--descriptor-type A2A \
--descriptors "{\"a2a\":{\"agentCard\":{\"schemaVersion\":\"0.3\",\"inlineContent\":$(echo $CARD | jq -Rs .)}}}" \
--record-version "1.0.0" \
--region us-east-1
A2A has more required fields: protocolVersion, capabilities, defaultInputModes, defaultOutputModes, and skills[]. The url field is where the A2A spec expects an HTTP URL — we put the runtime ARN because AgentCore’s URL structure is derivable from the ARN, and this is how AWS’s own samples do it.
Step 4 — Approve the records
Since we didn’t enable auto-approval, both records sat in DRAFT:
+----------------------+----------------+--------+
| name | recordId | status |
+----------------------+----------------+--------+
| finance_tracker_mcp | Q9myeyGaqv2W | DRAFT |
| agui_document_agent | 5nfN5yhH6aOu | DRAFT |
+----------------------+----------------+--------+
The lifecycle is DRAFT → PENDING_APPROVAL → APPROVED. Two API calls:
for RID in Q9myeyGaqv2W 5nfN5yhH6aOu; do
aws bedrock-agentcore-control submit-registry-record-for-approval \
--registry-id U7fQe0ZSCr5zdBBw --record-id $RID --region us-east-1
aws bedrock-agentcore-control update-registry-record-status \
--registry-id U7fQe0ZSCr5zdBBw --record-id $RID \
--status APPROVED --status-reason "Initial demo seed" --region us-east-1
done
In a real org, submit-for-approval would be the publisher action and the status update would be a separate role (a curator). Here we wore both hats.
Step 5 — Search the catalog
This is the payoff — and where the registry earns the “semantic” adjective. There are two search surfaces:
Control plane (list-registry-records) gives you exact listing, no search:
aws bedrock-agentcore-control list-registry-records \
--registry-id U7fQe0ZSCr5zdBBw --region us-east-1
Data plane (search-registry-records) gives you hybrid semantic + keyword retrieval. This is the one that matters:
aws bedrock-agentcore search-registry-records \
--search-query "I want to record how much I spent on groceries" \
--registry-ids U7fQe0ZSCr5zdBBw \
--region us-east-1
Which returns only the MCP record — the A2A agent, while describing “finance” in its card, is less of a match for “record how much I spent.” The search is picking up intent (“record” → add_expense, “spent” → expense-tracking tools), not keyword overlap. Semantic search indexing took ~60 seconds after approval; initial queries returned empty.
Two ways to consume the registry from an agent
Once records exist, how does an agent actually use them? Two paths, and they compose.
Path A: SDK call (deterministic, 5 lines)
The registry’s data plane is a regular AWS API. Inside any agent:
import boto3, json
agentcore = boto3.client("bedrock-agentcore")
REGISTRY_ID = "U7fQe0ZSCr5zdBBw"
def discover_finance_tools(query: str):
hits = agentcore.search_registry_records(
searchQuery=query,
registryIds=[REGISTRY_ID],
maxResults=5,
)["registryRecords"]
for r in hits:
if r["descriptorType"] == "MCP":
server = json.loads(r["descriptors"]["mcp"]["server"]["inlineContent"])
tools = json.loads(r["descriptors"]["mcp"]["tools"]["inlineContent"])["tools"]
return server, tools
The agent decides when to discover. Typical pattern: call search_registry_records at startup, build a dynamic tool list, then connect to whichever runtimes/gateways the records point to.
Path B: Registry’s own MCP endpoint (conversational)
The registry itself speaks MCP. It exposes:
https://bedrock-agentcore.us-east-1.amazonaws.com/registry/U7fQe0ZSCr5zdBBw/mcp
Point your agent at this as an MCP server and the LLM can call search_registry_records as a tool mid-conversation. User says “track my groceries” → LLM decides to discover → calls the registry → gets back the finance_tracker_mcp record → opens that MCP server → calls add_expense. Zero hardcoded knowledge of any downstream service.
Path A is a compile-time decision; Path B is a runtime decision. The right one depends on how dynamic your tool set actually is.
The IAM you need
Your agent’s execution role already needs bedrock-agentcore:SearchRegistryRecords (data plane) and, for Path B, bedrock-agentcore:InvokeRegistryMCP. The BedrockAgentCoreFullAccess managed policy covers both. If you’re scoping down, resource-restrict to the specific registry ARN.
Wiring it into our deployed agent — and the VPC-endpoint footgun
We added a discover_services Strands tool to the already-running AG-UI document agent, deployed via Terraform, rebuilt the container, rolled it out. The LLM started calling the new tool correctly on prompts like “search the registry for finance”.
Then the tool timed out.
An error occurred (504) when calling the SearchRegistryRecords operation
(reached max retries: 4): Gateway Timeout
From our laptop the same API call returned both records in 300ms. From inside the VPC-locked runtime, it 504’d four times and died.
Our infra has a standard hardened setup: the runtime lives in private subnets with interface endpoints for bedrock-agentcore, bedrock-runtime, cognito-idp, ecr.api, ecr.dkr, logs, xray, sts, and an S3 gateway endpoint. There is no NAT gateway, no IGW. That’s on purpose — the only way out is through interface endpoints, which gives you a crisp security boundary.
The com.amazonaws.us-east-1.bedrock-agentcore interface endpoint works for InvokeAgentRuntime and related APIs, but does not route SearchRegistryRecords. The request reaches the endpoint (we get HTTP 504 back, not a connection timeout), but the upstream registry service isn’t reachable through it at time of writing. This isn’t transient — every retry 504s the same way.
The fix options, ranked by cost:
- Accept it. For most organizations, the registry’s primary consumers are not VPC-locked runtimes. They’re build systems, CI, IDE plugins, frontends, and ops dashboards — all of which run with public egress. The agent-calling-registry pattern is valid but not the primary use case.
- Proxy via Lambda. Put a thin Lambda outside the VPC that calls the registry and returns JSON. The agent invokes the Lambda through
bedrock-agentcore:InvokeAgentRuntime(already allowed via the interface endpoint). Adds a hop but keeps the VPC clean. - NAT gateway. ~$35/mo + data transfer, gives the runtime full public egress, registry search works. Broadest blast radius; use only if multiple services have the same problem.
We went with option 1 and graceful error handling in the tool. The moral: before planning to consume the registry from a VPC-locked runtime, prototype it from the runtime itself, not from your laptop. The VPC endpoint surface and the public API surface are not the same set.
How this is not AgentCore Gateway
Gateway and Registry sound similar on the surface — both help agents use tools they didn’t hardcode. They solve different layers, and mixing them up leads to weird designs.
| Gateway | Registry | |
|---|---|---|
| What it does | Runs tools — wraps Lambdas/APIs into a live MCP endpoint | Lists things — catalog metadata, no execution |
| Scope | One team’s tools bundled for one agent’s use | Cross-org catalog of many gateways, MCP servers, agents |
| Returns | Tool invocation results | Pointers + metadata (ARNs, URLs, schemas) |
| Contains | MCP tools only | MCP servers, A2A agents, skills, custom |
| Governance | IAM only | IAM + approval workflow |
| Search | tools/list — whatever this gateway exposes | Semantic + keyword across everything |
The clean composition is: a Gateway’s MCP endpoint gets published as a record in the Registry. You need the Registry precisely because Gateway #1 doesn’t know Gateway #2 exists.
When it’s worth the complexity (and when it isn’t)
Skip the registry if:
- You have one agent, one MCP source, one team.
- Your tool set changes infrequently — once a quarter, with a code review.
- You own both publisher and consumer.
Use the registry if:
- Multiple teams publish tools/agents you want to make discoverable.
- Agents need to discover capabilities dynamically (new MCP server published Tuesday → in use Wednesday without a redeploy).
- Compliance requires an approval trail before an agent can consume a tool.
- Humans and AI both need to browse a catalog (the registry’s MCP endpoint supports conversational exploration).
For a solo learning project? Overkill. But it’s the pattern that matters at scale, and the control-plane APIs are cheap to experiment with.
The mental model, one more time
Without registry:
agent ──hardcoded ARN──▶ mcp_tools_server
──hardcoded ARN──▶ (add more by redeploying)
With registry:
agent ──search "finance"──▶ Registry
◀── [mcp_tools_server, finance-gateway, credit-agent]
──connects to each──▶ (MCP/Gateway/other agents)
The registry doesn’t make a single agent smarter. It makes a collection of agents and tools navigable. That’s a different problem — one you don’t have yet on day one, and the one that eats you alive by year two.
What we actually shipped
- Registry
U7fQe0ZSCr5zdBBwin us-east-1, IAM-authorized, manual approval workflow. - MCP record
finance_tracker_mcp(3 tools) and A2A recordagui_document_agent(2 skills). discover_servicesStrands tool on the AG-UI document agent, wired through Terraform, env-var-configured.- Hybrid search confirmed working from outside the VPC; 504s from inside the VPC due to the endpoint-coverage gap.
The registry works. The deployment pattern needs one more design decision (NAT, Lambda proxy, or external-only consumers) before it’s production-ready for VPC-locked agents. That decision depends on your threat model, not the registry.
References
- AWS docs: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/registry.html
- AWS samples:
awslabs/agentcore-samples→01-tutorials/10-Agent-Registry/ - MCP server schema: https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json
- A2A agent card spec: https://a2a-protocol.org/
More recent articles
- AgentCore Harness, Inside Out - 24th April 2026
- MCP Apps Explained: How AI Agent Shows Live Widgets Inside the Chat - 23rd April 2026