v1's git-pull pattern has four limitations the MCP model fixes:
The MCP server model couples every fetch to Cognito identity, returns specific resources rather than bundles, and logs the requester + the resource + the version end-to-end. Same architectural pattern as the OAuth abstraction (Step 6.n): provider-as-data, identity-aware, audited.
| Property | Definition | What it eliminates |
|---|---|---|
| Real-time | Every fetch hits the MCP server. No local cache between consumer and source of truth. | Lockfile drift. Stale SDK in a cartridge build six months after the last pull. |
| Protected | Cognito JWT on every request. Authorization gated by Request Roles assignment. | Off-boarding gap. GitHub-vs-Forge identity drift. Unaudited SDK consumption. |
| Versioned | Each MCP resource carries its semver. Cartridge declares the SDK version it builds against; server returns that version's resources. | Implicit version coupling. Silent breaking changes. Inability to deprecate. |
The Model Context Protocol (MCP) is Anthropic's open protocol for exposing tools and resources to AI agents. Two relevant pieces:
The transport is HTTPS at port 443 with JSON-RPC. From a Forge perspective, an MCP server is "a Lambda behind API Gateway HTTP API that speaks MCP," which fits the standard cartridge pattern.
Anthropic-published clients (Claude Code, Claude Desktop) speak MCP natively. Non-Claude clients (the future pull-sdk.sh) wrap MCP with a small HTTPS+JSON-RPC client.
The MCP server exposes resources at URIs that mirror the v1 path structure plus version awareness:
| Resource URI | Returns |
|---|---|
forge-sdk://manifest | The bundle manifest (paths, version, compatibility, endpoints) |
forge-sdk://VERSION | The current published version string |
forge-sdk://shared/modules/jwt-verify.js | The hand-rolled JWT verifier source |
forge-sdk://shared/modules/hmac.js | HMAC helpers |
forge-sdk://shared/modules/log.js | Structured log helpers |
forge-sdk://shared/modules/oauth/client.js | OAuth client factory |
forge-sdk://shared/modules/oauth/adapters/salesforce.js | Salesforce capability adapter |
forge-sdk://cartridges/_template/lambda/handler.js | Scaffold handler |
forge-sdk://cartridges/_template/backend.tf.snippet | Scaffold backend.tf snippet |
forge-sdk://docs/cartridge-walkthrough.md | Build walkthrough (full markdown) |
forge-sdk://docs/handler-reference.md | Handler.js reference |
forge-sdk://docs/backend-reference.md | backend.tf reference |
forge-sdk://docs/bedrock-pattern.md | Bedrock invocation pattern |
forge-sdk://docs/oauth-pattern.md | OAuth consumption pattern |
forge-sdk://docs/observability-contract.md | *-complete log schema |
forge-sdk://Doctrine.md | Full doctrine (all sections) |
forge-sdk://Doctrine.md#2.7 | Specific doctrine section (granular fetch) |
forge-sdk://.claude/skills/forge-sdk/SKILL.md | Agent skill |
Version selection: by default the server returns the latest stable for the requester's pinned version. The cartridge's manifest can declare sdk_version: "1.1.0" and the server scopes resource resolution to that version. Explicit version override is via a query string: forge-sdk://shared/modules/jwt-verify.js?version=1.0.0.
Every MCP request carries a Cognito JWT in the standard MCP auth header. Three layers of gate:
sdk-readers Cognito group, granted via the Request Roles cartridge. New federated users land without this group; they request it; system admin approves; access begins.Service accounts (for CI builds) get a longer-lived JWT issued via a system-admin-approved Cognito client. Audit log distinguishes service from interactive accounts.
sdk-readers via Request Roles, gets approved.pull-sdk.sh (or Claude Code) authenticates with that identity and pulls.The server tracks every resource at every version. CMS publishes a new content row; the row gets a version stamp; the MCP server can serve any historical version on request.
When a consumer asks for forge-sdk://shared/modules/jwt-verify.js with no version override, the server resolves to the highest version that satisfies the consumer's pinned manifest:
cartridge manifest: { sdk_version: "1.1.0" }
request: forge-sdk://shared/modules/jwt-verify.js
server resolves to: latest patch version <= 1.1.x, returning jwt-verify.js@1.1.0
The response includes a deprecation field when the consumer's pinned version is below current major or minor:
{
"resource": "forge-sdk://shared/modules/jwt-verify.js",
"version": "1.1.0",
"content": "...",
"deprecation": {
"warn": "You are pinned to SDK 1.1.0; current is 2.3.0. Upgrade required by 2027-06-01.",
"current": "2.3.0",
"sunset": "2027-06-01T00:00:00Z"
}
}
Consumer CLIs print the warning. CI integrations can fail the build past a sunset date. Operators see the deprecation rate in the obs Cartridges tab.
Past a sunset date, the server can refuse to serve the version. Response status 410 Gone with an explanation. Cartridge build fails loudly. Operators get a runbook entry.
Every MCP request emits one log line on completion. The obs Cartridges tab joins on the line for the SDK-consumers view.
{
"level": "info",
"msg": "mcp-sdk-fetch-complete",
"user": "<cognito-username>",
"service": false, // true for CI service accounts
"resource": "forge-sdk://shared/modules/jwt-verify.js",
"requested_version": "1.1.0",
"served_version": "1.1.0",
"deprecated": false,
"client": "pull-sdk.sh/1.1.0", // or "claude-code/0.7.x"
"prompt_context_hash": "<short hash of the agent's user-message>", // null for human-driven
"durationMs": 14
}
Operators query the log for "who has been pulling deprecated resources," "which consumers are on stale SDK," "which agents are pulling which docs in which prompts." Compounds into a real visibility surface, not just developer ergonomics.
From the human author's perspective, almost nothing changes:
$ ./pull-sdk.sh v2.0.0
Authenticating with Forge SSO... # device flow opens browser to Console
Browser opened: paste the verification code: ABCD-EFGH
Authenticated as mtang.
Pulling forge-sdk @ v2.0.0 from console.paycargo.org/mcp/forge-sdk ...
forge-sdk://manifest ok
forge-sdk://shared/modules/jwt-verify.js ok
forge-sdk://shared/modules/hmac.js ok
... (29 resources)
Pulled forge-sdk v2.0.0
./forge-sdk/ 29 resources written
./forge-sdk.lock version pin + per-resource SHAs
The script is a thin wrapper. Auth via device flow (the OAuth pattern Anthropic uses for Claude CLI). Per-resource fetches over HTTPS+JSON-RPC. Lock file captures the version + per-resource SHA so reproducibility is preserved.
Claude Code connects to the MCP server via its standard MCP client. It fetches resources on demand during a cartridge build, never materializing the bundle to disk. The agent's tool-use trace shows every fetch alongside the prompt that triggered it. The audit log line gets a non-null prompt_context_hash.
Cartridge Terraform today stages SDK files into lambda/_shared/ via local_file reading from ./forge-sdk/shared/modules/. v2 has three options:
| Option | How it works | Trade-off |
|---|---|---|
| A. Pre-apply pull (recommended) | CI step runs pull-sdk.sh v2.x.x before terraform plan. Materializes ./forge-sdk/ the same way v1 does. Terraform code is identical. |
Adds a step to CI. Same model as today; lowest disruption. |
| B. Terraform HTTP data source | An http data source fetches each resource directly from MCP at plan time. local_file reads from the data source. |
No pre-apply step but needs auth token in TF state; tricky. |
| C. Custom Terraform provider | A forge-sdk Terraform provider that knows how to authenticate with MCP and expose resources as TF data. |
Cleanest UX, most engineering work. |
v2.0 ships Option A. Option C is a future iteration if consumers demand it.
The MCP server does not own the content. It serves content from the CMS cartridge (Step 6.s). Reading flow:
The MCP server is a stateless cache + protocol adapter on top of the CMS. Reads are hot (heavy caching); writes happen in the CMS, not here. This separation lets us evolve the protocol layer (add new MCP features, change the URI scheme) without touching the content store.
Chicken-and-egg risk: the MCP server cartridge needs the SDK to build itself. Resolved by sequence:
Doctrine.md, docs/*.md, SKILL.md, the cartridge template, the shared modules JS) as v1.x.x CMS rows.System admins keep at least one current local v2.x checkout for the duration of the v1-to-v2 migration. This is the Doctrine 8.9 fallback: a git copy that diff-matches CMS published content.
Once v2 ships, the MCP server is critical-path infrastructure: every cartridge build depends on it. Three mitigations:
| Mitigation | Description |
|---|---|
| Read replica | The MCP server fronts a DDB Global Table replica. Read failover automatic across regions. |
| CDN cache | CloudFront caches responses with appropriate cache headers per resource. Stale-while-revalidate for content reads; no-cache for the manifest. |
| Git mirror fallback | Doctrine 8.9: every CMS publish triggers a git mirror update. If MCP is down, consumers fall back to the git copy (last-known-good) via the legacy pull-sdk.sh --fallback-git flag. |
SLA target: 99.95% MCP availability. Read failover < 30 seconds. Git fallback usable indefinitely (system admins maintain the mirror).
Five PRs after Request Roles + SSO are live.
--legacy-git) for the bootstrap period.
SDK version 2.0.0 ships at PR 5 apply. v1.x git tags remain available as fallback. Consumers upgrade on their own timeline.