Perfsys — AWS Consulting Partner
Amazon Bedrock AgentCore hosting an MCP server that exposes AWS Marketplace seller operations as tools
AWS BedrockArchitectureAPIAWS Consulting

MCP Servers on Amazon Bedrock AgentCore: Lessons From Automating AWS Marketplace

Published on Aug 19, 2026

pattern

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":

Who
Model
Server
Model
Server
Human
Server
What happens
Decides it needs the live pricing dimensions first
describe_product runs DescribeEntity and returns the dimension table
Reads it, picks the milestones, builds a spec, asks for a preview
Runs every validation rule and returns the change set plus a token
Reviews the exact payload and approves
Submits, polls to a terminal state, reports the real outcome

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   ARM64

In 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:

Field or rule
ChangeName
ChangeSetName
Offer Description
Dimension Name
Dimensions
Cancelled offers
What actually applies
Letters only, no digits. "ConsolidateTo6Milestones" is rejected outright.
No parentheses, commas or slashes. "EUR 9,600" fails.
255 characters, and never shown to the buyer. The Name is what they see.
Around 50 characters. The docs claim 5, which is a documentation bug.
Cannot be deleted from a live product, ever. Retiring one means renaming it.
Still report State: Released. Check the availability date, not the state.

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.

Component
Runtime CPU
Runtime memory
Gateway invocations
Identity
Cognito
Marketplace APIs
Rate
$0.0895 per vCPU-hour, billed per second
$0.00945 per GB-hour, 128 MB minimum
$0.005 per 1,000, plus $0.02 per 100 tools indexed monthly
No charge when used through Runtime or Gateway
Free below 10,000 monthly active users
No per-call charge

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.

Component
Runtime
Gateway
Identity
Observability
Memory
Browser
Code Interpreter
Using it?
Yes
Narrowly
Inbound half
Yes
No
No
No
Why
Hosts the server. Scale-to-zero, plus endpoint versioning for staged rollout.
Clean public URL and managed inbound OAuth. Not for its headline feature, since our tools are already MCP-native.
Validates tokens. The outbound half exists for third-party APIs; we use an execution role.
CloudWatch and OTel traces, plus vended CPU and memory metrics.
Runtime requires stateless operation, and the confirm token is deterministic so that no state is needed.
Every operation is an API call. Nothing to automate in a browser.
A model generating and running code against a money-moving API is the failure mode we are designing against.

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.

Want AWS Operations You Can Actually Audit?
Want AWS Operations You Can Actually Audit?

We design and run AWS infrastructure for startups and SMBs, including the automation and guardrails around it.

Tell Us About Your Setup
Chevron right
Explore AWS Managed Services
Chevron right
Eugene Orlovsky

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.

Explore Our Case Studies

View all Case Studies
Chevron right

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.