Skip to main content

ADR-023: A Measured Context Budget for Wiring MCP Servers

FieldValue
StatusProposed
Date2026-09-13
Decision Draftersproduct-owner + cloud-architect + developer-experience-engineer under scope_id mcp-aws-azure-capability-pages-2026-09-13
Decision MakersHITL approval required before this ADR may be marked Accepted
ScopeHow AWS and Azure MCP servers are admitted to the catalog, wired to products, and documented
Review triggerTR-1 through TR-4 below

Context

The /mcps page was scored 6/10. Two sections earned their place; two did not. The weak half shared one root cause: the catalog could say a server existed without anyone having proven it ran, fenced it, or given a reader a reason to care.

That gap produced three concrete defects, each measured rather than asserted:

  1. Boilerplate presented as a guide. Every generated tool prompt was one template — Use the <tool> tool from the <server> MCP server for my current task: <description> — with the description pasted twice. docs/docs/mcp/aws.mdx was 817 lines of it. For awslabs-api the tool descriptions are AWS's own deprecation notice, so the copy-paste prompt instructed the agent to announce a deprecation.
  2. Silent content loss. _meta.playbooks authored 5 entries; the page rendered 3. build_playbooks() excludes any playbook whose servers are not all measured/verified, and nothing surfaced the exclusion. 40% of authored content was invisible, including to its own authors.
  3. The blocked claim was accurate for its environment. _meta.roadmap recorded awslabs-iam, awslabs-cloudtrail and awslabs-cloudwatch as blocked by ModuleNotFoundError in the canonical image. Re-tested on 2026-09-13 on the host at identical pins: all three start correctly. Both records were accurate for their respective environments. The block persists in the canonical image due to a PYTHONPATH leak in the runbooks Dockerfile (/app:/venv/lib/python3.13/site-packages), not due to the servers themselves. Cleared or on the host, they function normally.

Underneath all three sits the decision this ADR exists to settle. A wired MCP server is not a command an agent chooses to run — it is a standing tool surface loaded into every session of every product in its _targets. The 15 AWS servers under consideration would have added an unmeasured 150-250 tools, and six of them can mutate AWS resources. The cost is paid by every session, forever. The benefit is paid once, on a documentation page.

Before this ADR, no tool-budget policy existed anywhere under .claude/mcps/ — a grep returned zero hits. Wiring decisions were therefore taken without a number to evaluate them against.

Decision

Admission to the catalog, wiring to products, and documentation quality are three separate gates. A server may pass one and fail the next.

IDDecisionWhere recorded
D1Measure every candidate server; wire only a curated subset; unwired servers render as measured/available§1 budget, §2 tiers
D2Azure mirrors AWS: the same capability groups, populated from the measured @azure/mcp namespaces, so both pages read as one system§6
D3Replace generated per-tool boilerplate with authored setup (single account and landing zone) + one scenario per server§4
D4Resolve the awslabs-api → aws-mcp migration by probing the successor and recording what is measuredConsequences

1. A measured context budget, with a derived ceiling

Session weight is defined as core-plugin tools (60, fixed) + measured tools of every product-tier server whose _targets contains this product.

The ceiling is 400 tools per product session. It is derived, not invented: the measured incumbent maximum today is 384 (the largest current project: 324 product + 60 core), plus 16 tools of headroom. The policy it encodes is "no product session may become materially heavier than the heaviest one we already ship and operate." Exceeding it requires an explicit HITL waiver recorded at _meta.budget_waivers[] with measured before/after figures.

The gate runs after measurement and before any _targets value is finalised. A wiring decision taken before the gate has a number to evaluate is a forecast, not a design.

2. Three wiring tiers, not two

TierShapeSession costMeaning
Wired_targets non-emptyFull tool count, every sessionMeasured, fenced, and under budget
Available, not wired_targets: [], _status: measuredZeroProbed and printable on the page; loadable on demand
Roadmap_meta.roadmap entryZeroNamed, with the specific reason it is not admitted

The middle tier is the load-bearing one. It lets the catalog tell the truth about a server's capability without charging every session for it.

3. A verified read-only fence is a precondition of wiring, fail-closed

Not a warning, not a follow-up. Unverifiable fence ⇒ not wired ⇒ not added. The fence must be verified against the server's own --help at the pinned version, never inferred from a flag name.

Applying this rule rejected 5 of 12 candidates on evidence:

ServerWhy rejected
awslabs-postgres--allow_write_query does not gate its separate create_cluster tool, which creates a real Aurora cluster
awslabs-lambda-toolPure invoke surface; no read-only mode exists
awslabs-stepfunctionsPure invoke surface; no read-only mode exists
awslabs-security-agentAuto-provisions an IAM role and S3 bucket; generic call_api with no fence
awslabs-sns-sqsIts only flag (--allow-resource-creation) does not gate purge_queue, delete_message or send_message

The case of awslabs-sns-sqs carries genuine use-case value, yet it is still excluded and recorded with a named re-evaluation trigger. A fence that does not cover the destructive tool is not a fence.

4. Business scenarios are authored in the SSOT, never generated

Three new SSOT fields carry what a generator cannot invent: _group (the AWS Labs capability taxonomy), _scenario (one real outcome, naming only tools that exist in measured output), and _setup (both a single-account and a multi-account Landing Zone path, expressed purely through environment-variable indirection). _scenario.measured_count has minimum: 1, which forces the no-data case down an explicit no_data branch rather than letting a zero be quietly styled as a finding.

5. Scaffolds stay behind the curtain

INVEST, 5W1H, and why / what_if_missing / business_value / purpose / critical_thinking are generative scaffolds — they are how an agent guarantees it covered every angle. They belong in coordination logs, this ADR, and the rules layer. They are never customer-facing, and neither is a CEO/CTO/CFO/CDO/CSO persona taxonomy. Pages are organised by AWS Labs' own capability groups and speak in outcomes. Enforced by a grep gate over rendered strings.

6. Azure mirrors AWS through namespaces

The azure MCP server runs in namespace mode, exposing 60 measured namespaces across five capability groups: identity, network, compute, storage, and analytics. These namespace groups are assigned in the SSOT at .claude/mcps/.mcp-enterprise.json mcpServers.azure._namespace_groups, forming a set identical to the measured tool names. The Azure page renders a capability group when it has either Azure servers (wired to that group) OR Azure namespaces (assigned to that group), enabling Azure content to populate alongside AWS. Tool counts per group are summed per brand, ensuring that Azure namespaces appear only on Azure pages and AWS tools only on AWS pages.

7. Public outputs carry no tenant facts

Per-tenant data in screenshots, cost figures, resource counts, or alarm states is a customer's live security and cost posture. Publishing it discloses reconnaissance detail, breaches confidentiality expectations (APRA CPS 234-class), goes stale and then contradicts reality, and identifies the customer. Rule: public outputs carry only vendor-level facts (server, tools/list tool counts, read-only status). Tenant terms are read from _meta.tenants at run time, never hard-coded. What if kept: the customer finds their own posture on a vendor site → trust and contract loss; an attacker learns which alarms fire and which subscriptions are off; a board reader sees per-month cost figures beside agents and concludes low value.

Consequences

Gains

  • Every printed tool count traces to a probe, and every wired server to a verified fence.
  • Nine servers become printable at zero session cost via the available-not-wired tier.
  • Four write-capable servers are kept out of production sessions on evidence, before anyone had to discover the gap through an incident.
  • The budget ceiling converts an unbounded "add another server" pressure into an explicit, reviewable waiver.
  • Public outputs display "N MCP tools" per item and group (no per-month cost figures), and orbit tiles use official AWS Architecture Icons (release 07312026) unmodified, ensuring readers recognize the actual AWS services agents operate on.

Group ordering and narrative

The AWS MCP orbit displays five groups in C-suite story order, each defined in the catalog:

  1. Cost & Operations — "See your spend, alarms, and who changed what."
  2. Developer Tools — "Find who can reach admin. Get current Terraform guidance."
  3. Infra & Deployment — "See what runs in containers and serverless."
  4. Data & Analytics — "Query S3 Tables and Aurora data. Check DynamoDB designs first."
  5. Integration & Messaging — "Answer questions from your REST APIs; more next."

Narrative arc: The sequence answers the executive questions, in this order: What is our spend and who changed what? (Cost ↓) → Who can access admin? (Risk ↓) → What services are running? (Time-to-value ↑) → What data can we query? (Insight) → What's next? (Connect).

Video sequence: A presenter navigates the orbit in rank order, using each group's catalog line as narration.

SceneOn screenNarration source
OpeningHub: "Your AI agents · 144 tools connected"capability_showcase.AWS.hub.tools_measured
Step 1Cost & Operations highlighted; "Step 1 of 5" slideCost & Operations line
TransitionAnimated dot travels ring to next group(visual)
Step 2Developer Tools highlighted; "Step 2 of 5" slideDeveloper Tools line
TransitionDot travels to Step 3(visual)
Step 3Infra & Deployment highlighted; "Step 3 of 5" slideInfra & Deployment line
TransitionDot travels to Step 4(visual)
Step 4Data & Analytics highlighted; "Step 4 of 5" slideData & Analytics line
TransitionDot travels to Step 5(visual)
Step 5Integration & Messaging highlighted; "Step 5 of 5" slideIntegration & Messaging line
TransitionDot travels from Step 5 back to Step 1; the ring loop closes(visual)
ClosingOrbit with roadmap section visible"more next" narrative from Step 5 line

Every number and line in this table is read from docs/static/data/mcp-catalog.json at build time; the video producer MUST re-read it rather than copy today's values. Current tool count (2026-09-13): 144. Out of scope: tenant facts, resource counts, per-month costs, per-tenant resource states.

Losses / open risks

  • The catalog is now smaller than the ambition. Five of the fifteen named AWS servers do not ship as wired capability. That is the correct outcome, but it is a real reduction against the original request.
  • The 400 ceiling is anchored to today's incumbent, not to a measured degradation point. We do not know the tool count at which agent instruction-adherence actually degrades. The ceiling is a defensible convention, not a discovered threshold.
  • Two acceptance criteria cannot be closed by a command. Playbook contrast quality and page voice require a non-producer reader verdict. A grep proves "CFO" is absent; it cannot prove the prose is good. Treating a mechanical check as proof here would be GREP_PROXY_FOR_JUDGMENT.
  • awslabs-ecs breaks on any fresh install of the container image. The server pins fastmcp>=3.2.0 with no upper bound. On 2026-09-05, fastmcp 4.0.3 removed add_tool_transformation, causing startup crashes. The server remains wired to 4 products and was corrected in the SSOT with --with fastmcp<4 in the launch args. This found a second class of problem: hidden by the PYTHONPATH leak, a write-capable server was wired while in a permanently broken state in the canonical test image.
  • The measuring instrument had structural defects. The stock list-mcp-tools.py probe has no per-read timeout (one stalled server blocks all others) and inherits the runbooks image's PYTHONPATH leak and credential-mount issues. Two durable fixes remain out of scope: (1) scope PYTHONPATH in the .devcontainer/runbooks/Dockerfile instead of image-wide, and (2) pass -e PYTHONPATH= plus read-write mount of the container user's AWS config directory in the mcp:tools Taskfile step. Both are infrastructure-engineer decisions documented as named next steps, not silent gaps.
  • awslabs-api remains wired to 6 products while deprecated upstream. Its successor mcp-proxy-for-aws uses different profile semantics, so a like-for-like swap would silently change which credentials six products use. The swap is deliberately deferred.

Alternatives Considered

AlternativeWeighted scoreVerdictWhen to reconsider
Wire all 15 servers to all relevant products34/100Deferred — adds 150-250 unmeasured tools to every session and admits 6 write-capable servers without verified fencesIf per-server lazy tool loading lands, making session weight a function of use rather than configuration
Catalog-only; probe nothing, wire nothing41/100Deferred — zero cost and zero risk, but reproduces the exact unverified quality that scored 2/10If the page's purpose narrows to link-directory only
Wire everything, fence at the agent-permission layer instead of the server48/100Deferred — permission prompts are per-session and bypassable; a server-level fence is structuralIf a deny-list gains per-tool granularity that is enforced server-side
Three tiers + measured budget + fail-closed fence86/100AdoptedTR-1..TR-4 below
Fork the AWS Labs servers to add missing read-only modes29/100Deferred — solves the fence gap properly but creates a permanent maintenance obligation across 5 forksIf a rejected server becomes business-critical and upstream declines the fence

Review triggers

  • TR-1 — A product session measures over 400 tools without a recorded waiver.
  • TR-2 — Upstream adds a genuine read-only mode to any of the 5 rejected servers.
  • TR-3mcp-proxy-for-aws reaches profile parity with awslabs-api, unblocking the swap.
  • TR-4 — Evidence emerges of a real degradation threshold, replacing the 400 convention with a measured number.

Evidence

ClaimArtifact
PO stories and acceptance criteria (10 stories, 42 ACs, 7 risks)tmp/adlc-framework/coordination-logs/mcp-aws-azure-capability-pages-2026-09-13/product-owner.json
Budget derivation, fence design, Azure mapping, D4 probetmp/adlc-framework/coordination-logs/mcp-aws-azure-capability-pages-2026-09-13/cloud-architect.json
Live re-test refuting 3 stale blocked claimstmp/adlc-framework/evidence/mcp-aws-server-retest-2026-09-13.md
Server catalog and pins.claude/mcps/.mcp-enterprise.json (40 servers)
Upstream deprecationawslabs/mcp src/aws-api-mcp-server/MIGRATION.md
Terraform supersessionPyPI yank reason on awslabs.terraform-mcp-server
PYTHONPATH leak diagnosis (image vs. host)tmp/adlc-framework/evidence/mcp-container-pythonpath-leak-2026-09-13.md
Bounded MCP tool probe (AWS servers, cleared environment)tmp/adlc-framework/evidence/mcp-tools-probe-aws-bounded-2026-09-13.json

A note on one rejected package. awslabs.terraform-mcp-server is yanked on PyPI with the reason "Superceeded by https://github.com/hashicorp/terraform-mcp-server". This settles a question that had been open in planning: Terraform's MCP server is HashiCorp's, and it was already correctly wired here as a digest-pinned container with ENABLE_TF_OPERATIONS=false. No change was required — the evidence simply confirmed an existing decision, which is worth recording so the question is not reopened.

Status note

Status is Proposed. Accepted is reserved for HITL sign-off. The three environment-specific blocked claims are documented accurately in the SSOT (reflecting both image and host measurements); azure-devops remains blocked because it was not re-tested this session — environment-specific claims must be labeled as such, never promoted on the basis of a different environment's test alone.