Akshay Parkhi's Weblog

AgentCore Registry: The Missing Yellow Pages for AI Agents

14th April 2026

How we stopped hardcoding ARNs. What we learned when we published an MCP server and an A2A agent. 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. You get an ARN back. You paste it into the frontend’s config.ts. You 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. 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?” The honest answer is “grep our Slack.”

This is the problem the AgentCore Registry exists to solve. It’s a discovery catalog. It is a cross-account, cross-team index of the agents, MCP servers, skills, and other resources your organization 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. It makes them searchable, including semantically, via a hybrid LLM + keyword engine. It gates publication behind an approval workflow so garbage can’t flood the catalog.

Registry record vs ARN: different layers of the same stack

Our first mistake was to assume 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. You get it 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. It is 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:

ARNRegistry record
Created byRuntime deployYou, explicitly
ContentsJust an IDRich metadata + schemas + pointer to ARN
SearchableNoYes — semantic + keyword
Discoverable by other agentsNo (must be told)Yes — via MCP endpoint
GovernanceIAM onlyIAM + approval + deprecation
VersioningRuntime versions onlyRecord versions + lifecycle state
AnalogyIP addressDNS entry + Yellow Pages listing

If you only ever built one agent, you don’t need a registry. You do need one if you’re in an org where someone else might want to use what you built. You also need one if you want your agent to discover what someone else built.

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:

TypeWhat lives hereExample
MCPAn MCP server + its tool listA finance-tracker server with add_expense, list_expenses, summarize_spending
A2AAn agent-to-agent card (an agent’s public profile)A document-authoring agent with skills research_topic, update_document
Agent SkillsReusable skill definitions + markdownA “refund-processing” skill with input/output schemas
CustomAny schema you inventInternal prompt templates, eval suites, dataset pointers

The first three are protocol-specific. They assume you follow either the Model Context Protocol (MCP) or the 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. Custom lets you move fast while you find 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 connects your corporate OIDC provider and lets end-users search the registry with their own tokens. We would point it at the same Cognito pool that the stack already uses elsewhere. CUSTOM_JWT is the right choice for production frontends, and IAM is the right choice for backends and build systems.

approvalConfiguration.autoApproval defaults to false. With that default, every new record follows three steps:

  1. It starts as DRAFT.
  2. It moves to PENDING_APPROVAL when you submit it.
  3. It becomes searchable only 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

We needed three attempts to find the right record schema. The CLI says the inlineContent fields must “conform to the MCP protocol specification”. That 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:

  1. schemaVersion: "2025-12-11" is not the one in the public MCP spec docs. We found it only when we read awslabs/agentcore-samples notebooks.
  2. Minimal server body. The server body is just {name, description, version}. If you add capabilities, remotes, endpoint, etc. (all valid per MCP’s server.json), validation fails.
  3. The tools wrapper is {"tools": [...]}, not just an array.
  4. inlineContent is a JSON string, not a JSON object. The API rejected every example we tried to pass as a nested object. You must stringify the whole thing, then embed it. 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 A2A spec expects an HTTP URL in the url field. We put the runtime ARN there, because you can derive AgentCore’s URL structure from the ARN. This is also 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. It is 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

This returns only the MCP record. The A2A agent describes “finance” in its card, but it is less of a match for “record how much I spent”. The search matches 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. The typical pattern:

  1. Call search_registry_records at startup.
  2. Build a dynamic tool list.
  3. 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. The LLM can then call search_registry_records as a tool mid-conversation. The flow:

  1. The user says “track my groceries”.
  2. The LLM decides to discover.
  3. The LLM calls the registry.
  4. The registry returns the finance_tracker_mcp record.
  5. The LLM opens that MCP server.
  6. The LLM calls add_expense.

The agent has 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 want narrower permissions, restrict the resource 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. We deployed it via Terraform. We rebuilt the container. We released it. The LLM started to call 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. It has interface endpoints for bedrock-agentcore, bedrock-runtime, cognito-idp, ecr.api, ecr.dkr, logs, xray, and sts. It also has 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. It does not route SearchRegistryRecords. The request reaches the endpoint (we receive HTTP 504, 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:

  1. 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 these run with public egress. The agent-calling-registry pattern is valid but not the primary use case.
  2. 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, which the interface endpoint already allows. This adds a hop but keeps the VPC clean.
  3. NAT gateway. It costs ~$35/mo + data transfer. It gives the runtime full public egress, and registry search works. It has the broadest blast radius. Use it only if multiple services have the same problem.

We chose 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 confusing them leads to weird designs.

GatewayRegistry
What it doesRuns tools — wraps Lambdas/APIs into a live MCP endpointLists things — catalog metadata, no execution
ScopeOne team’s tools bundled for one agent’s useCross-org catalog of many gateways, MCP servers, agents
ReturnsTool invocation resultsPointers + metadata (ARNs, URLs, schemas)
ContainsMCP tools onlyMCP servers, A2A agents, skills, custom
GovernanceIAM onlyIAM + approval workflow
Searchtools/list — whatever this gateway exposesSemantic + keyword across everything

The clean composition is: you publish a Gateway’s MCP endpoint 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. You don’t have it yet on day one, and it eats you alive by year two.

What we actually shipped

  • Registry U7fQe0ZSCr5zdBBw in us-east-1, IAM-authorized, manual approval workflow.
  • MCP record finance_tracker_mcp (3 tools) and A2A record agui_document_agent (2 skills).
  • discover_services Strands tool on the AG-UI document agent, wired through Terraform, env-var-configured.
  • We confirmed hybrid search works from outside the VPC. From inside the VPC it returns 504s because of 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

More recent articles

This is AgentCore Registry: The Missing Yellow Pages for AI Agents by Akshay Parkhi, posted on 14th April 2026.

Next: MCP Apps Explained: How AI Agent Shows Live Widgets Inside the Chat

Previous: Beyond Tool Calling: A Practical Tour of Advanced MCP Concepts