
MCP Servers on Amazon Bedrock AgentCore: Lessons From Automating AWS Marketplace
Published on Aug 19, 2026
Table of Contents
- The Problem: Knowledge That Doesn't Scale
- What an MCP Server Actually Is
- AgentCore Runtime's Actual Contract
- Three Assumptions Worth Rechecking
- 1. You probably do not need a proxy in front of AgentCore
- 2. Dynamic Client Registration may not be your problem
- 3. Check the CLI invocation you are copying
- The Interesting Constraint: Gating Writes Without State
- What We Encode: The Traps
- What It Costs to Run
- Which AgentCore Pieces You Actually Need
- When This Is Worth Doing
- FAQ
- The Problem: Knowledge That Doesn't Scale
- What an MCP Server Actually Is
- AgentCore Runtime's Actual Contract
- Three Assumptions Worth Rechecking
- 1. You probably do not need a proxy in front of AgentCore
- 2. Dynamic Client Registration may not be your problem
- 3. Check the CLI invocation you are copying
- The Interesting Constraint: Gating Writes Without State
- What We Encode: The Traps
- What It Costs to Run
- Which AgentCore Pieces You Actually Need
- When This Is Worth Doing
- FAQ
Every AWS Marketplace private offer we issue used to depend on one laptop, one AWS profile and a folder of Python scripts.
That worked fine. It also meant the expensive part — knowing which undocumented field limit will reject your change set, and which payment schedule AWS silently refuses to publish — lived in one engineer's head and one directory.
So we moved it behind a Model Context Protocol (MCP) server designed to run on Amazon Bedrock AgentCore Runtime. Here is the architecture, the three assumptions we had to correct along the way, and what it actually costs to operate.
The Problem: Knowledge That Doesn't Scale
AWS Marketplace seller operations run through two APIs: marketplace-catalog for writes and marketplace-agreement for reads. Both exist only in us-east-1. Every write is an asynchronous change set, so StartChangeSet returns an ID immediately while the real work happens in a queue you have to poll.
None of that is difficult. What is difficult is that the API rejects a lot of reasonable-looking input for reasons the documentation does not cover, and some of those rejections only surface at the very last step, after an offer is fully built.
The value in this kind of tooling is never the API calls. It is knowing which undocumented rule is about to reject your change set — and that is exactly the knowledge that does not survive being written down in a wiki nobody rereads.
What an MCP Server Actually Is
An MCP server contains no model. It answers two questions — tools/list ("what can you do?") and tools/call ("run this with these arguments") — and ours answers them with plain Python and boto3. There is no inference, no prompt and no planning inside it.
The reasoning lives entirely in the client. Walking through a real request, "create the migration offer for EUR 9,600":
Every decision belongs to the model. Every action is deterministic Python. That split is the whole design.
"Bedrock AgentCore" sounds like it must include a model. It does not. Runtime is container hosting with an MCP-shaped contract, so hosting a tool server there involves zero inference. You are renting a microVM, not a model.
AgentCore Runtime's Actual Contract
AWS is specific about what it will host. Miss any of these and deployment either fails or behaves strangely:
Transport stateless streamable-HTTP only
Host 0.0.0.0
Port 8000
Path POST /mcp
Protocol tools/list and tools/call
Session platform injects Mcp-Session-Id - your server must accept it
Container ARM64In Python that is a handful of lines:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("aws-marketplace-seller", host="0.0.0.0", stateless_http=True)
@mcp.tool()
def describe_offer(offer_id: str) -> dict:
"""Full offer report with grants joined to dimension names.
An offer whose state is Released but whose availability has passed
is CANCELLED - AWS expresses cancellation through the date, never
through a state transition.
"""
...
mcp.run(transport="streamable-http")One packaging detail cost us time. The mcp 2.0 release renamed FastMCP to MCPServer and dropped host and stateless_http from the constructor. AgentCore's documented contract assumes the 1.x shape, so we pinned mcp below 2.0 rather than reverse-engineer the equivalents while everything else was also new.
Notice too that the tool docstring is not decoration. It is the only place a connecting model learns that a Released offer with a past availability date is cancelled. Tool descriptions are prompt surface, and they deserve the same care as the code.
Three Assumptions Worth Rechecking
1. You probably do not need a proxy in front of AgentCore
Guidance written even a few months ago says the Runtime invocation URL embeds a signed ARN and a qualifier query parameter, so you must front it with API Gateway or an ALB to present a clean URL to MCP clients. AgentCore Gateway now serves one directly, at https://{gatewayId}.gateway.bedrock-agentcore.{region}.amazonaws.com/mcp, and it publishes the OAuth protected-resource metadata itself — an unauthenticated call returns 401 with a WWW-Authenticate header pointing at it. That deletes a whole component from the architecture.
2. Dynamic Client Registration may not be your problem
To expose the server in claude.ai as a custom connector, the documented requirement is OAuth 2.1 with PKCE and Dynamic Client Registration. Cognito has no native DCR support, which sends people off to build a Lambda /register shim. But the connector form has optional OAuth Client ID and Secret fields, and supplying a pre-registered client means DCR never runs. A standard Cognito app client using authorization-code with PKCE and a callback of https://claude.ai/api/mcp/auth_callback should be sufficient. Test that before building the shim — it is a cheap experiment and an expensive assumption.
3. Check the CLI invocation you are copying
It is npm install -g @aws/agentcore, then agentcore create --protocol MCP and agentcore deploy. Plenty of write-ups still use agentcore configure. This space is moving quickly enough that verifying commands against current docs is worth the two minutes.
The Interesting Constraint: Gating Writes Without State
Releasing a private offer moves real money. We wanted a hard review step: nothing gets submitted that a human has not seen first. The obvious implementation is a pending-operations table — the client previews, the server stores the payload, the client confirms by ID.
That design does not survive AgentCore. Runtime is stateless with per-session microVM isolation, so there is no reliable place to keep a pending operation between two calls. Adding a database to hold it would mean adding a database to hold something that exists for thirty seconds.
The fix is to make the confirmation derivable rather than stored:
def token(changes, entity_version):
"""Deterministic, so no server-side pending state is needed."""
payload = json.dumps(changes, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(f"{payload}|{entity_version}".encode())
return digest.hexdigest()[:16]The token hashes the exact change set together with the entity version it was built against. Three properties fall out of that, none of which need storage:
- You cannot submit a change set that was never previewed, because you would have no way to produce a matching token.
- Editing the change set after the preview invalidates the token.
- A product that changed underneath you between preview and submit invalidates it too, because the entity version is part of the hash.
This does not limit what can be written. It guarantees that a reviewable artifact existed first, which is the difference between an audited write and a surprise. Statelessness turned out to make the design better, not worse.
What We Encode: The Traps
Two constraints deserve special mention, because they pass every other check and fail only at the moment you try to release the offer — after the whole thing has been built and reviewed:
TOO_MANY_BACKDATED_CHARGES Provide up to 1 scheduled payments before AvailabilityEndDate.
INVALID_CHARGE_DATES Provide a last charge date that is before AgreementEndDate.The first means at most one payment date may fall on or before the acceptance deadline. AWS refuses to publish a schedule with more than one. Our initial reading was that late acceptance would cause the installments to pile up and bill at once — it does not, because the change set never publishes in the first place.
The second is subtler. The agreement end date is computed from the actual acceptance date, so the binding case is acceptance today. A six-month term with six monthly charges starting this month does not fit: the last charge lands on or after the end. You cannot see that by reading the spec; you see it when the release fails.
Both are unit tests now. The regression case is a real offer spec that AWS rejected for both reasons on the same afternoon, which makes it a genuine test rather than a hypothetical one. Everything else the validator enforces:
Two more only turned up by calling the live API rather than reading about it. DescribeEntity takes EntityId on the request but returns EntityIdentifier on the response. And ListEntities wants the entity type unversioned, as "Offer", while change sets want it versioned, as "Offer@1.0" — passing the versioned form to ListEntities is a validation error.
If you are wrapping an API you already know well, write the validator before the client. Ours immediately caught a bad offer spec that we had written ourselves, using a rule we had added to it an hour earlier.
What It Costs to Run
AgentCore is consumption-based with no provisioned component, so the stack scales to zero between uses and there is no idle floor to speak of.
For internal use at a few hundred tool calls a month, that lands somewhere around ten to fifty cents. Even if the container never idled out and held half a gigabyte warm for a full month, memory alone would come to $3.45.
Model inference is zero, and that is a direct consequence of the architecture. Because this is a tool server rather than an agent hidden behind a single tool, no LLM runs on the AWS side at all. AWS's own comparable example at 100 interactions a day shows $17 a month of model inference against $0.40 of Runtime. Choosing the tool-server shape removes that line entirely and puts the reasoning on a subscription you are already paying for.
The line item to watch is CloudWatch, not compute. In AWS's published example, 1.5 GB of verbose logs cost $7.23 while Runtime cost $0.40 — logs were eighteen times the compute. A poll loop that logs every DescribeChangeSet response will do exactly that to you.
We log state transitions rather than individual polls, and set log retention explicitly in the infrastructure code instead of leaving it on never-expire.
Which AgentCore Pieces You Actually Need
AgentCore is seven composable services, and the marketing material understandably presents all of them. We use four, and two of the exclusions are deliberate rather than incidental.
When This Is Worth Doing
This pattern earns its keep when three things are true at once: the domain has real traps that cost you rework, the operations repeat often enough that encoding them pays back, and you want an audit trail of what changed and why.
If you are wrapping a well-documented CRUD API that never surprises you, an MCP server is a thin layer over a thin layer. Skip it and call the SDK.
Where we are today, stated plainly: the server runs, 57 tests pass without touching AWS, and the read and preview paths are verified against live offers. Deployment to AgentCore Runtime and the claude.ai connector are the next two steps in that order, and we are testing the Dynamic Client Registration assumption before building anything that depends on it. The most useful output so far is not the deployment — it is that a validation rule now fails in a test suite in twenty milliseconds instead of at the end of a two-minute change set.
FAQ
No. AgentCore Runtime is container hosting with an MCP-shaped contract. If you deploy a tool server, no inference runs and there are no model costs — the reasoning happens in whichever client connects to it.
Yes, provided it is reachable over public HTTPS using streamable HTTP. AgentCore Gateway gives you a clean URL and serves the OAuth protected-resource metadata. If the connector needs authentication, supplying a pre-registered OAuth client ID and secret avoids Claude's Dynamic Client Registration path, which Cognito does not support natively.
Runtime is $0.0895 per vCPU-hour and $0.00945 per GB-hour, billed per second with no provisioned component. Low-volume internal use typically lands under a dollar a month. Watch CloudWatch log ingestion, which commonly costs more than the compute it monitors.
They can, but pair every write with a preview that returns the exact payload plus a confirmation token derived from it. On a stateless runtime, derive that token by hashing the payload rather than storing pending state, so the guarantee holds without adding a database.
us-east-1, always. The Catalog and Agreement APIs exist nowhere else, and a profile defaulting to another region fails with a connection error that reads like a network fault rather than a configuration mistake.
No. MCP is tool interoperability — a contract for exposing capabilities. It carries no shared planning, memory or delegation semantics. Model your service as a specialist capability behind a few explicit tools rather than as a peer agent.

We design and run AWS infrastructure for startups and SMBs, including the automation and guardrails around it.
Eugene Orlovsky
CEO & Founder | Serverless architect with 10+ years of hands-on experience designing cloud-native architectures on AWS, backed by multiple AWS certifications. He is writing bridges deep technical expertise with real-world business strategy, covering topics from AWS best practices to scaling tech-driven organizations.
AWS Experts, On-Demand
Need to move fast? Our cloud team is ready to scale, secure, and optimize your systems. Get serverless expertise, 24/7 support, and seamless CI/CD pipelines when you need it most.
Please accept cookies to load the booking widget.
