Skip to content

AI Engineering

Kiro Trusts One File. Here Is How I Stopped Editing It by Hand

Kiro's enterprise MCP registry is one JSON file behind one URL: whatever is in it, developers see; whatever is not, never starts. That makes the file a control point — and a hand-edited control point is a liability. I rebuilt mine as a generated artifact: AWS Agent Registry as the approved catalog, AgentCore Gateway as the only remote MCP endpoint, and a small publisher that turns approvals into the file Kiro reads. Here are the five decisions, the six things the docs did not tell me, and what I still have not verified.

 ·  11 MIN READ


Alexandre Agius

Alexandre Agius

AWS SOLUTIONS ARCHITECT

SHARE

Kiro has a feature most teams discover late: an enterprise MCP registry. An administrator puts one URL in the Kiro profile, Kiro fetches the JSON file behind it at startup and every 24 hours, and from then on the IDE and the CLI only show the MCP servers listed in that file. Anything else is hidden and never starts. Developers can still override a server’s environment variables, headers and timeout, but not what it points to, and not the list itself. The Kiro documentation is refreshingly blunt about it: servers not in the registry are hidden.

That is a real control point, and I like it. What I did not like was how I was operating it.

The problem with a file you edit by hand

The pattern I started from is the one described in Govern MCP Tools at Scale with Kiro and AgentCore Gateway: put an Amazon Bedrock AgentCore Gateway in front of your remote tools, then point Kiro at a static registry file served from Amazon S3 and Amazon CloudFront. It works. My file listed six servers: five local uvx servers from the awslabs collection and one remote documentation server.

The trouble is everything the file does not have. There is no approval trail: whoever can write to the bucket decides what every developer in the organization sees, and nothing records who approved a server or why. There is no search: a developer wondering “do we already have a tool for X?” reads JSON or asks in a channel. And there is drift: the file describes what someone believed was true when they last edited it, not what is actually deployed and allowed. A file that gates what a few hundred developers can run should not depend on someone remembering to update it correctly on a Friday afternoon.

So I turned the file into an output.

Three pieces, one direction of flow

The design has three pieces, and traffic only flows one way through them.

AWS Agent Registry is the source of truth. It went GA at the end of August and it is exactly the missing piece: a governed catalog of MCP servers, tools and skills, with manual approval by a curator, CloudTrail behind every state change, and its own MCP endpoint so the catalog can be searched in natural language from an IDE chat. Every server that exists in my setup, remote or local, is a record in this registry, and nothing reaches a developer without a curator having approved it.

AgentCore Gateway is the only remote MCP endpoint developers reach. Behind one URL it fronts four targets: the AWS Knowledge MCP server, Context7 for library documentation, and two internal tool servers on AWS Lambda (a simulated deployment tool and a team runbook and on-call lookup). Inbound authentication is a JWT from the identity provider (Amazon Cognito stands in for the corporate IdP in this build). A Cedar policy engine runs in ENFORCE mode: the platform engineering group may use every tool, the application development group gets an explicit allow-list and a forbid on production deployments. Every decision lands in vended CloudWatch logs. If you have read my post on the LLM gateway versus the tool gateway, this is the tool gateway role, and nothing else.

A publisher turns approvals into the Kiro file. Every record state change in the registry emits an Amazon EventBridge event. A small Lambda function wakes up, reads the approved records, rebuilds the Kiro registry file, validates it, and publishes it to S3 behind CloudFront. A reconcile rule re-runs it every 15 minutes in case an event was missed.

What Kiro ends up seeing is seven entries. corp-tools is the gateway: every remote tool, behind authentication, policy and audit. corp-catalog is the registry’s own MCP endpoint, so developers can search the approved catalog from the IDE chat before building something that already exists. The remaining five are the local uvx servers imported from the old static file, under the same names, so the overrides developers already had in their mcp.json keep matching. The remote documentation server from the old file is gone from the list: it now lives behind the gateway as a target.

Five decisions, and what each one rules out

Most of the value of a design note is in the alternatives it rejects, so here are mine.

1. The registry is the truth; the Kiro file is generated. The rejected alternative is the hand-edited file I started with. Generation is what gives me approval, audit and search for free, because the registry already has them.

2. Kiro lists the gateway, the catalog endpoint and local servers. Nothing else. The rejected alternative is listing each upstream remote server directly for Kiro. That would bypass the gateway, and with it the policy engine and the logs. Local stdio servers are the exception because they cannot be proxied: they run on the laptop.

3. Publication is default-deny. An approved record is catalog knowledge; whether Kiro should list it is a second, explicit decision, carried by a kiroDistribution: direct flag in the record’s _meta block. The rejected alternative was to publish every approved record, which is how the upstream servers behind the gateway would leak into the file and let developers bypass it. Records without the flag stay in the catalog, searchable, and never reach Kiro.

4. The publisher reads the registry’s MCP endpoint, not the control plane. It authenticates with its own client credentials and only sees the discovery plane, which exposes approved revisions. Drafts cannot leak by construction. The rejected alternative, the control-plane list API, sees drafts too, and one filter bug would publish an unapproved server to every developer.

5. Publication is fail-safe. A document that fails validation, or that suddenly comes out empty, is refused; S3 versioning keeps every file that was ever served. The rejected alternative, a blind overwrite, has an obvious failure mode: one bad publication and every developer loses every tool at the next Kiro sync.

Two smaller choices on the gateway side are worth a line each. Authorization is a claim gate on group membership plus Cedar, not Cedar alone: the claim gate rejects a user in no allowed group at the door with a 403, where Cedar alone would still let them reach tools/list (and get an empty answer, but reach it). And the group is copied into a plain string claim by a Cognito pre-token Lambda, because string tags are the documented Cedar case; matching the cognito:groups array directly is not.

What the documentation did not tell me

This is the part I would have paid for a week earlier.

The Kiro registry format has no OAuth field. A remote entry has a url and headers, and that is all. Kiro’s native OAuth flow exists for servers declared in agent configurations and mcp.json, not for registry entries. So a governed remote server carries Authorization: Bearer ${KIRO_MCP_TOKEN}, and the developer exports that variable before starting Kiro (a small script gets a 12-hour token from the IdP). This has a sharp edge: with no token set, no browser opens. Kiro keeps the unset placeholder verbatim in the header, the gateway answers 401, Kiro starts its own OAuth discovery, finds the Cognito authorization server and stops with “does not support dynamic client registration”. Cognito has no dynamic client registration, and a registry entry has nowhere to carry a pre-registered client ID. Do not promise your developers that a login window will appear. Also add the variable to Kiro’s approved environment variables setting: the documentation says only approved variables are expanded in registry entries, and a client update could start honoring that.

The gateway’s built-in semantic search tool cannot be named in a Cedar policy. x_amz_bedrock_agentcore_search is filtered by Cedar like any tool, but a policy that names it fails to deploy (the target does not exist in the gateway). Only a permit with an unconstrained action exposes it, which is why my platform engineering group sees 12 tools and the application development group sees 11.

The registry’s MCP endpoint does not match its documentation. The list operation takes a recordType argument where the docs show a filters list, and an empty result comes back as a plain sentence, not JSON. Read tools/list from the live endpoint before writing a client against it.

URL synchronization renames records. When a record is synchronized from a live server URL, the registry renames it after the server’s own serverInfo.name. Track records by ID, never by name.

The AWS CLI did not know the service yet. On the version I had, the agent-registry namespace was missing; a current boto3 had it (with the crt extra if you authenticate with aws login). Check your CLI version before you blame your credentials.

The loop is fast. Approving a record puts it in the Kiro file in 11 seconds; deprecating one removes it in 10. CloudFront’s one-minute TTL is invalidated on each publication. The slow part is Kiro itself, which re-reads the file at startup and every 24 hours.

What proved it, and what I did not verify

My implementation ships a live end-to-end script with 34 checks, all passing on the last run: the file validates against Kiro’s documented schema, lists exactly the approved records flagged for distribution, leaks no upstream or behind-gateway endpoint, and CloudFront serves nothing but the registry file (the publish report, which explains why each record was included or excluded, stays private). Real kiro-cli transcripts show it: a developer in the application group sees 11 tools and gets a production deployment refused by name of the policy that refused it; a platform engineer sees 12 and deploys. Twenty-two offline unit tests cover the converter’s safety rules, the Cedar rendering and the Lambda handlers, and one of them fails the build if a real identifier ever lands in a tracked file.

What I have not verified is the Kiro IDE itself in registry mode. Everything above was exercised with the Kiro CLI, which uses the same MCP client, plus validation against the documented file schema and a read of the IDE’s registry code. The screenshot of the IDE showing “N servers hidden” and the two corporate entries is still on my list. I would rather write that sentence than let you assume I took the picture.

The honest limits

Everything the registry does is enforced on the laptop, and the Kiro documentation says so. A developer with administrator rights on their machine can edit Kiro. The hard stop is not the file, it is the network: allow MCP egress only to the gateway and the registry endpoints, and the file becomes a convenience layer on top of a real boundary rather than the boundary itself.

Sign-in is two identities today. Kiro signs in through IAM Identity Center; the gateway signs in through Cognito. In production you federate the user pool with the corporate IdP, or put the IdP directly on the gateway and the registry, so that the person who opened Kiro is the person the policy engine reasons about.

The exported-token workflow is functional and unglamorous: one command in the morning, restart Kiro from that shell. The alternatives are a proxy that does the browser login on the developer’s behalf with a pre-registered public client, or an IdP that supports dynamic client registration. Both are more moving parts than I wanted for a first version.

And one setting I left on for the demo that you should not: the gateway runs with exceptionLevel=DEBUG, so a denied call tells the client which Cedar policy denied it. That is wonderful for a transcript and wrong for production, where callers should see a generic denial while the full decision stays in the logs.

Honest recommendation

If you have adopted Kiro’s enterprise registry and your file is edited by hand, you have built the control point and left the control out. The fix is not a better editing process; it is making the file an output of something that already has approval, audit and search, and AWS Agent Registry is now that thing. Keep the gateway as the single remote endpoint so the file only ever names one URL for tools, flag distribution to Kiro as a separate decision from approval, read the discovery plane so drafts cannot leak, and refuse to publish anything that fails validation. Then close the real boundary with egress rules, because the file will never be one.

Everything above fits in a sandbox account and an afternoon: one registry, one gateway, one small publisher. Approve a record and watch it land in Kiro eleven seconds later.

ABOUT THE AUTHOR

Alexandre Agius

Alexandre Agius

AWS Solutions Architect

Passionate about AI & Security. Building scalable cloud solutions and helping organizations leverage AWS services to innovate faster. Specialized in Generative AI, serverless architectures, and security best practices.

ONE LETTER A MONTH · NO TRACKER · UNSUBSCRIBE ANYTIME

CONTINUE READING

Related dispatches

Comments

Sign in to leave a comment